OO 언어로 객체 상태를 구현합니까?


11

나는 자동차 경주를 시뮬레이트하는 기본 코드 머신의 구현을 포함하는 Java 코드를 받았다. 이것은 고전적인 컴퓨터 과학 상태 머신이 아니라 여러 상태를 가질 수 있고 일련의 계산을 기반으로 상태를 전환 할 수있는 객체 일뿐입니다.

문제를 설명하기 위해 자동차 상태에 대한 일부 상수 (예 : OFF, IDLE, DRIVE, REVERSE 등)를 정의하는 중첩 열거 형 클래스가있는 Car 클래스가 있습니다. 이 같은 Car 클래스에는 기본적으로 자동차의 현재 상태를 켜고 계산을 한 다음 자동차 상태를 변경하는 큰 스위치 문으로 구성된 업데이트 기능이 있습니다.

내가 볼 수있는 한, Cars 상태는 자체 클래스 내에서만 사용됩니다.

내 질문은, 이것이 위에서 설명한 자연 상태 머신의 구현을 처리하는 가장 좋은 방법입니까? 가장 확실한 해결책처럼 들리지만 과거에는 항상 "스위치 설명이 잘못되었다"고 들었습니다.

여기서 볼 수있는 주요 문제는 상태를 추가 할 때 (필요한 경우) switch 문이 매우 커져 코드가 다루기 어려워 유지 관리하기 어려울 수 있다는 것입니다.

이 문제에 대한 더 나은 해결책은 무엇입니까?


3
당신의 설명은 나에게 상태 머신처럼 들리지 않습니다. 그것은 단지 각각의 내부 상태를 갖는 많은 자동차 객체처럼 들립니다. 실제 작동 코드를 codereview.stackexchange.com에 게시하십시오 . 그 사람들은 작업 코드에 대한 피드백을 제공하는 데 능숙합니다.
Robert Harvey

아마도 "상태 머신"은 잘못된 단어 선택이지만, 기본적으로 자체 내부 상태를 전환하는 많은 자동차 객체가 있습니다. UML 상태 다이어그램으로 시스템을 웅변 적으로 설명 할 수 있기 때문에 게시물 제목을 지정했습니다. 뒤늦은 견해로는 문제를 설명하는 가장 좋은 방법은 아닙니다. 게시물을 편집하겠습니다.
PythonNewb

1
여전히 코드 검토에 코드를 게시해야한다고 생각합니다.
Robert Harvey

1
나에게 상태 머신처럼 들린다. object.state = object.function(object.state);
robert bristow-johnson 2012

수락 된 답변을 포함하여 지금까지 제공된 모든 답변은 스위치 문이 잘못된 것으로 간주되는 주된 이유를 놓칩니다. 개방 / 폐쇄 원칙을 준수하지 않습니다.
덩크

답변:


13
  • State Pattern을 사용하여 Car를 일종의 상태 머신으로 바꿨습니다 . 상태 선택 에는 no 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 사실, 실제로는 실제로 교환 할 수있는 조각을 맵에 저장하고이를 매개 변수 파일에서로드 된 ID를 기반으로 가져 오기 때문에 이렇게 작성했습니다. 일반적으로 맵에 저장하는 것은 오브젝트 자체가 아니라 오브젝트가 비싸거나 비 정적 상태가 많은 경우 작성자입니다.
Tulains Córdova 2018 년

16

스위치 문이 잘못되었습니다

객체 지향 프로그래밍에 나쁜 이름을 부여하는 것은 이런 종류의 단순화입니다. 사용하는 것은 if단지 switch 문을 사용하는 등 "나쁜"로한다. 어느 쪽이든 다형성 적으로 디스패치하지 않습니다.

소리에 맞는 규칙이 있어야하는 경우 다음을 시도하십시오.

스위치 문은 복사본이 두 개있는 순간 매우 나빠집니다.

코드베이스의 다른 곳에서 복제되지 않은 스위치 문은 때때로 악의적이지 않을 수 있습니다. 사건이 공개되지 않았지만 캡슐화되어 있다면 다른 사람의 사업은 아닙니다. 특히 클래스로 리팩토링하는 방법과시기를 알고있는 경우. 그렇다고해서 꼭 그럴 필요는 없습니다. 지금하는 것이 덜 중요 할 수 있기 때문입니다.

스위치 문에 점점 더 많은 것을 쏟아 부 으려고하거나 사례에 대한 지식을 퍼뜨 리거나 단순히 사본을 만드는 것이 그렇게 나쁘지 않기를 바라는 경우 사례를 별도의 클래스로 리팩토링 할 때입니다.

스위치 문 리팩토링에 대한 몇 가지 소리를 읽을 시간이 있다면 c2는 switch 문 냄새에 대해 매우 균형 잡힌 페이지를 가지고 있습니다 .

OOP 코드에서도 모든 스위치가 나쁜 것은 아닙니다. 그것은 당신이 그것을 사용하는 방법과 이유입니다.


2

자동차는 상태 머신의 한 유형입니다. 스위치 문은 수퍼 상태 및 하위 상태가없는 상태 머신을 구현하는 가장 간단한 방법입니다.


2

스위치 문은 나쁘지 않습니다. "스위치 통계가 나쁘다"라고 말하는 사람들의 말을 듣지 마십시오! switch 문을 사용하는 특정 용도는 하위 클래스를 에뮬레이트하기 위해 switch를 사용하는 것과 같은 반 패턴입니다. (그러나 if와 함께이 반 패턴을 구현할 수도 있으므로 나쁘지 않은 것 같아요!).

구현이 잘 들립니다.더 많은 상태를 추가하면 유지 관리가 어려워집니다. 그러나 이것은 단순한 구현 문제가 아닙니다. 행동이 다른 여러 상태의 객체를 갖는 것은 그 자체가 문제입니다. 당신의 자동차가 25 개의 주를 가지고있는 이미징은 각각 다른 행동과 주 전환에 대한 다른 규칙을 보여주었습니다. 이 동작을 지정하고 문서화하는 것은 엄청난 작업입니다. 당신은해야합니다 수천 상태 전이 규칙을! 의 크기 switch는 더 큰 문제의 증상 일뿐입니다. 따라서 가능하면이 길로 내려 가지 마십시오.

가능한 해결 방법은 상태를 독립 하위 상태로 나누는 것입니다. 예를 들어, REVERSE는 실제로 DRIVE와 다른 상태입니까? 자동차 상태는 엔진 상태 (OFF, IDLE, DRIVE)와 방향 (FORWARD, REVERSE)의 두 가지로 나 broken 수 있습니다. 엔진 상태와 방향은 대부분 독립적이므로 논리 복제 및 상태 전환 규칙을 줄입니다. 상태가 적은 개체가 많을수록 상태가 많은 단일 개체보다 관리하기가 훨씬 쉽습니다.


1

귀하의 예에서 자동차는 단순히 고전적인 컴퓨터 과학적 의미에서 상태 머신입니다. 그것들은 작고 잘 정의 된 상태 세트와 어떤 종류의 상태 전환 로직을 가지고 있습니다.

첫 번째 제안은 전환 논리를 자체 함수 (또는 언어가 일류 함수를 지원하지 않는 경우 클래스)로 분리하는 것을 고려하는 것입니다.

두 번째 제안은 전이 로직을 상태 자체로 분리하여 자체 기능 (또는 언어가 퍼스트 클래스 기능을 지원하지 않는 경우 클래스)을 갖는 것을 고려하는 것입니다.

두 가지 방식에서 상태 전이 프로세스는 다음과 같습니다.

mycar.transition()

또는

mycar.state.transition()

물론 두 번째는 자동차 클래스에 싸여서 첫 번째처럼 보일 수 있습니다.

두 시나리오 모두에서 새 상태 (예 : DRAFTING)를 추가하면 새 유형의 상태 개체를 추가하고 새 상태로 특별히 전환되는 개체 만 변경하면됩니다.


0

그것은 얼마나 큰에 따라 switch 있습니다.

귀하의 예에서, 내가 생각할 switch수있는 다른 상태가 없기 때문에 a 는 괜찮습니다.Car . 그래서 시간이 지남에 따라 더 커지지는 않습니다.

유일한 문제는 큰 스위치가있는 경우 case 에 많은 지시 사항이 대해 개별적인 개인 메소드를 만드십시오.

때때로 사람들은 국가 디자인 패턴을 제안합니다 하지만 복잡한 논리를 처리 할 때 더 적합하고 많은 개별 작업에 대해 서로 다른 비즈니스 결정을 내립니다. 그렇지 않으면 간단한 문제에는 간단한 해결책이 있어야합니다.

일부 시나리오에서는 상태가 A 또는 B 인 경우에만 태스크를 수행하지만 C 또는 D는 수행하지 않는 메소드를 가지거나 상태에 따라 매우 간단한 조작으로 여러 메소드를 가질 수 있습니다. 그렇다면 하나 이상의 switch진술이 더 좋을 것입니다.


0

이것은 디자인 패턴은 물론 누구나 객체 지향 프로그래밍을하기 전에 사용되었던 일종의 구식 스테이트 머신처럼 들립니다. C와 같은 스위치 문이있는 모든 언어로 구현할 수 있습니다.

다른 사람들이 말했듯이 switch 문에는 본질적으로 잘못된 것이 없습니다. 대안은 종종 더 복잡하고 이해하기 어렵습니다.

스위치 케이스의 수가 엄청나게 커지지 않는 한, 그 일은 상당히 관리하기 쉬울 수 있습니다. 읽을 수있게하는 첫 번째 단계는 각 경우의 코드를 함수 호출로 바꾸어 상태의 동작을 구현하는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.