Реалізація стану об'єкта мовою ОО?


11

Мені було запропоновано деякий код Java, який імітує гонку автомобілів, яка включає реалізацію базової машини. Це не класична державна машина інформатики, а просто об'єкт, який може мати декілька станів і може перемикатися між своїми станами на основі ряду обчислень.

Щоб описувати лише проблему, у мене є клас Car, з вкладеним enum класом, який визначає деякі константи стану автомобіля (наприклад, OFF, IDLE, DRIVE, REVERSE тощо). У цьому ж класі автомобілів у мене є функція оновлення, яка в основному складається з великої заяви вимикача, яка вмикає поточний стан автомобілів, робить деякі розрахунки, а потім змінює стан автомобілів.

Наскільки я бачу, стан "Автомобілі" використовується лише в межах власного класу.

Моє запитання: чи це найкращий спосіб впоратися із впровадженням державної машини описаного вище характеру? Це звучить як найбільш очевидне рішення, але в минулому я завжди чув, що "заяви переключення погані".

Основна проблема, яку я бачу тут, полягає в тому, що оператор перемикання, можливо, може стати дуже великим, оскільки ми додамо більше станів (якщо це вважатиметься необхідним) і код може стати непростим і важким у обслуговуванні.

Що було б кращим рішенням цієї проблеми?


3
Ваш опис не здається мені державною машиною; це просто звучить як купа автомобільних предметів, кожен з яких має свій внутрішній стан. Розглянути можливість розміщення фактичного робочого коду на codereview.stackexchange.com ; ці люди дуже добре надають відгуки про робочий код.
Роберт Харві

Можливо, "державна машина" - це поганий вибір слів, але так, в основному у нас є купа автомобільних предметів, які вмикають власний внутрішній стан. Система красномовно може бути описана діаграмою стану UML, саме тому я назвав свою посаду такою. Заднім числом це не найкращий спосіб описати проблему, я редагую свою публікацію.
PythonNewb

1
Я все ще думаю, що вам слід розглянути можливість розміщення коду для перегляду коду.
Роберт Харві

1
мені звучить як державна машина. object.state = object.function(object.state);
Роберт Брістоу-Джонсон

Усі відповіді, надані до цього часу, включаючи прийняту відповідь, пропускають основну причину того, що заяви про перемикання вважаються поганими. Вони не дозволяють дотримуватися принципу відкритого / закритого типу.
Данк

Відповіді:


13
  • Я перетворив Автомобіль на державну машину сортування, використовуючи State Pattern . Повідомлення ні switchабо if-then-elseзаяви не використовуються для вибору стану.

  • У цьому випадку всі стани є внутрішніми класами, але це може бути реалізовано інакше.

  • Кожен стан містить дійсні стани, на які він може змінюватися.

  • Користувачу пропонується наступний стан у випадку, якщо це можливо більше, або просто підтвердити, якщо можливий лише один.

  • Ви можете його скласти і запустити для тестування.

  • Я використовував графічне діалогове вікно, тому що простіше було інтерактивно запустити його в Eclipse.

введіть тут опис зображення

Діаграма UML взята звідси .

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JOptionPane;

public class Car {

    private State state;
    public static final int ST_OFF=0;
    public static final int ST_IDDLE=1;
    public static final int ST_DRIVE=2;
    public static final int ST_REVERSE=3;

    Map<Integer,State> states=new HashMap<Integer,State>();

    public Car(){
        this.states.put(Car.ST_OFF, new Off());
        this.states.put(Car.ST_IDDLE, new Idle());
        this.states.put(Car.ST_DRIVE, new Drive());
        this.states.put(Car.ST_REVERSE, new Reverse()); 
        this.state=this.states.get(Car.ST_OFF);
    }

    private abstract class State{

        protected List<Integer> nextStates = new ArrayList<Integer>();

        public abstract void handle();
        public abstract void change();

        protected State promptForState(String prompt){
            State s = state;
            String word = JOptionPane.showInputDialog(prompt);
            int ch = -1;
            try {
                ch = Integer.parseInt(word);
            }catch (NumberFormatException e) {
            }   

            if (this.nextStates.contains(ch)){
                s=states.get(ch);
            } else {
                System.out.println("Invalid option");
            }
            return s;               
        }       

    }

    private class Off extends State{

        public Off(){ 
            super.nextStates.add(Car.ST_IDDLE);             
        }

        public void handle() { System.out.println("Stopped");}

        public void change() {
            state = this.promptForState("Stopped, iddle="+Car.ST_IDDLE+": ");
        }

    }

    private class Idle extends State{
        private List<Integer> nextStates = new ArrayList<Integer>();
        public Idle(){
            super.nextStates.add(Car.ST_DRIVE);
            super.nextStates.add(Car.ST_REVERSE);
            super.nextStates.add(Car.ST_OFF);       
        }

        public void handle() {  System.out.println("Idling");}

        public void change() { 
            state=this.promptForState("Idling, enter 0=off 2=drive 3=reverse: ");
        }

    }

    private class Drive extends State{

        private List<Integer> nextStates = new ArrayList<Integer>();
        public Drive(){
            super.nextStates.add(Car.ST_IDDLE);
        }       
        public void handle() {System.out.println("Driving");}

        public void change() {
            state=this.promptForState("Idling, enter 1=iddle: ");
        }       
    }

    private class Reverse extends State{
        private List<Integer> nextStates = new ArrayList<Integer>();
        public Reverse(){ 
            super.nextStates.add(Car.ST_IDDLE);
        }           
        public void handle() {System.out.println("Reversing");} 

        public void change() {
            state = this.promptForState("Reversing, enter 1=iddle: ");
        }       
    }

    public void request(){
        this.state.handle();
    }

    public void changeState(){
        this.state.change();
    }

    public static void main (String args[]){
        Car c = new Car();
        c.request(); //car is stopped
        c.changeState();
        c.request(); // car is iddling
        c.changeState(); // prompts for next state
        c.request(); 
        c.changeState();
        c.request();    
        c.changeState();
        c.request();        
    }

}

1
Мені це дуже подобається. Хоча я ціную головну відповідь і це захист тверджень про перемикання (я назавжди пам’ятаю це зараз), мені дуже подобається ідея цієї схеми. Дякую
PythonNewb

@PythonNewb Ви це запустили?
Tulains Córdova

Так, це прекрасно працює. Реалізація буде дещо іншою для коду, який я маю, але загальна ідея чудова. Я думаю, що я міг би розглянути можливість переміщення класів стану із класу, що додається.
PythonNewb

1
@PythonNewb Я змінив код на більш коротку версію, повторно використовуючи стан зміни / підказку для логіки введення, використовуючи абстрактний клас замість інтерфейсу. Це на 20 рядків коротше, але я тестував і працює так само. Ви завжди можете знайти більш стару, довшу версію, переглядаючи історію редагування.
Tulains Córdova

1
@Caleth Власне кажучи, я написав це так, тому що зазвичай роблю це в реальному житті, тобто зберігаю взаємозамінні фрагменти у картах і отримую їх на основі ідентифікатора, завантаженого з файла параметра. Зазвичай те, що я зберігаю на картах, - це не самі об'єкти, а їхні творці, якщо об’єкти дорогі або мають багато нестатичного стану.
Tulains Córdova

16

заяви переключення погані

Саме таке надмірне спрощення дає невірне ім'я об'єктно-орієнтованому програмуванню. Використання ifтак само "погано", як і використання оператора перемикання. У будь-якому випадку ви не поліморфно диспетчерські.

Якщо у вас є правило, яке підходить під звуковий звук, спробуйте це:

Заяви про перемикання стають дуже поганими, коли ви маєте дві копії.

Оператор перемикання, який не дублюється ніде в кодовій базі, іноді може впоратися із злом. Якщо справи не є загальнодоступними, але інкапсульовані, це справді нічим не займається. Особливо, якщо ви знаєте, як і коли переробити його на класи. Просто тому, що ти не можеш означати, що ти повинен. Це тому, що ви можете, що це менш важливо зробити це зараз.

Якщо ви виявляєте, що намагаєтеся все більше і більше речей вписати у висловлювання перемикача, поширюючи знання про випадки навколо, або бажаєте, щоб це було не так зло, просто зробити його копію, тоді прийшов час переробляти справи на окремі класи.

Якщо у вас є час прочитати більше декількох звукових записів про заяви перемикача рефакторингу, c2 має дуже добре збалансовану сторінку про запах заяви вимикача .

Навіть у коді OOP не кожен перемикач є поганим. Це, як ви його використовуєте, і чому.


2

Автомобіль - це тип державної машини. Оператори перемикання - найпростіший спосіб реалізувати машину стану, у якій не вистачає наддержав та підрядів.


2

Заяви про перемикання непогані. Не слухайте людей, які говорять такі речі, як "положення перемикання погані"! Деякі особливості використання операторів перемикання є антипатернними, наприклад, використовуючи перемикач для імітації підкласингу. (Але ви можете також реалізувати цей антипатерн за допомогою if, так що я здогадуюсь, чи це теж погано!).

Ваша реалізація звучить чудово. Ви правильні, це буде важко підтримувати, якщо ви додасте більше держав. Але це не лише питання реалізації - наявність об'єкта з багатьма станами з різною поведінкою сама по собі є проблемою. Зображення вашого автомобіля має 25 штатів, кожен з яких демонструє різну поведінку та різні правила переходів штату. Просто вказати та задокументувати таку поведінку було б величезним завданням. У вас будуть тисячі правил переходу держави! Розмір значень switchпросто був би симптомом більшої проблеми. Тож по можливості уникайте спуску по цій дорозі.

Можливий засіб - розбити державу на незалежні підстанції. Наприклад, чи справді REVERSE відрізняється від DRIVE? Можливо, стан автомобіля може бути розбитий на два: стан двигуна (вимкнено, холостий, привід) та напрямок (Вперед, ЗАПАС). Стан та напрямок двигуна, ймовірно, будуть здебільшого незалежними, тому ви зменшите дублювання логіки та правила переходу стану. Більше об'єктів із меншою кількістю станів набагато простіше керувати, ніж один об’єкт із численними станами.


1

У вашому прикладі автомобілі - це просто державні машини в класичному сенсі інформатики. Вони мають невеликий, чітко визначений набір станів і якусь логіку переходу стану.

Моя перша пропозиція - розглянути можливість переходу логіки переходу до власної функції (або класу, якщо ваша мова не підтримує функції першого класу).

Моя друга пропозиція - розглянути можливість переходу логіки переходу до стану, який би мав свою функцію (або клас, якщо ваша мова не підтримує функції першого класу).

В будь-якій схемі процес переходу в стан виглядав би приблизно так:

mycar.transition()

або

mycar.state.transition()

Другий, звичайно, може бути банально загорнутий у автомобільний клас, щоб виглядати як перший.

В обох сценаріях додавання нового стану (скажімо, ПРОЕКТУВАННЯ) передбачало б лише додавання нового типу об'єкта стану та зміна об'єктів, які спеціально переходять у новий стан.


0

Це залежить від того, наскільки вона switchможе бути великою .

У вашому прикладі, я думаю, що switchце нормально, оскільки насправді не існує жодного іншого стану, я можу подумати про те, що ваш Carміг би мати, так що він з часом не збільшуватиметься.

Якщо єдиним питанням є великий перемикач, де кожен caseмає безліч інструкцій, тоді просто створіть окремі приватні методи для кожного.

Іноді люди пропонують модель дизайну штату , але це більш доцільно, коли ви маєте справу зі складною логікою та заявляєте, що приймають різні бізнес-рішення для багатьох різних операцій. В іншому випадку прості проблеми повинні мати прості рішення.

У деяких сценаріях у вас можуть бути методи, які виконують завдання лише тоді, коли стан A або B, але не C або D, або кілька методів із дуже простими операціями, які залежать від стану. Тоді switchкраще було б одне чи кілька тверджень.


0

Це звучить як державна машина старої школи такого типу, яка використовувалася раніше, ніж хтось робив об'єктно-орієнтоване програмування, не кажучи вже про шаблони дизайну. Він може бути реалізований будь-якою мовою, яка має оператори переключення, наприклад, C.

Як вже говорили інші, в заявах про переключення нічого суттєво немає. Альтернативи часто складніші і важче зрозуміти.

Якщо кількість випадкових комутаторів не стане смішно великою, річ може залишатися досить керованою. Перший крок, щоб зробити його читабельним - це замінити код у кожному випадку на виклик функції для реалізації поведінки держави.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.