การใช้งานสถานะวัตถุในภาษา OO?


11

ฉันได้รับรหัส Java เพื่อดูซึ่งจำลองการแข่งรถซึ่งรวมถึงการใช้เครื่องรัฐขั้นพื้นฐาน นี่ไม่ใช่เครื่องคำนวณทางวิทยาศาสตร์คอมพิวเตอร์แบบดั้งเดิม แต่เป็นเพียงวัตถุที่มีหลายสถานะและสามารถสลับระหว่างสถานะต่าง ๆ ตามชุดการคำนวณ

เพื่ออธิบายเพียงแค่ปัญหาฉันมีคลาสรถยนต์พร้อมคลาส enum ซ้อนกันซึ่งกำหนดค่าคงที่สำหรับสถานะของรถยนต์ (เช่น OFF, IDLE, DRIVE, REVERSE และอื่น ๆ ) ภายในคลาสรถยนต์เดียวกันนี้ฉันมีฟังก์ชั่นการอัปเดตซึ่งโดยทั่วไปประกอบด้วยคำสั่งสวิตช์ขนาดใหญ่ซึ่งเปลี่ยนสถานะปัจจุบันของรถยนต์ทำการคำนวณบางอย่างและเปลี่ยนสถานะรถยนต์

เท่าที่ฉันเห็นรัฐ Cars ใช้ในชั้นเรียนของตัวเองเท่านั้น

คำถามของฉันคือนี่เป็นวิธีที่ดีที่สุดในการจัดการกับการใช้งานกลไกสถานะตามธรรมชาติที่อธิบายไว้ข้างต้นหรือไม่? มันฟังดูเหมือนโซลูชันที่ชัดเจนที่สุด แต่ก่อนหน้านี้ฉันเคยได้ยินมาเสมอว่า

ปัญหาหลักที่ฉันเห็นที่นี่คือคำสั่ง switch อาจมีขนาดใหญ่มากเมื่อเราเพิ่มสถานะเพิ่มเติม (ถ้าจำเป็น) และรหัสอาจไม่สะดวกและยากต่อการบำรุงรักษา

อะไรจะเป็นทางออกที่ดีกว่าสำหรับปัญหานี้?


3
คำอธิบายของคุณไม่เหมือนเครื่องรัฐสำหรับฉัน มันฟังดูเป็นเรื่องของวัตถุในรถซึ่งแต่ละคนก็มีสภาพภายในของตัวเอง พิจารณาการโพสต์ที่แท้จริงของคุณรหัสทำงานเพื่อcodereview.stackexchange.com ; คนเหล่านั้นเก่งในการให้ข้อเสนอแนะเกี่ยวกับรหัสการทำงาน
Robert Harvey

บางที "เครื่องรัฐ" เป็นคำที่ไม่ถูกต้อง แต่ใช่แล้วเรามีวัตถุรถยนต์มากมายที่เปลี่ยนสถานะภายในของตัวเอง ระบบสามารถอธิบายได้อย่างละเอียดด้วยแผนภาพสถานะ UML ซึ่งเป็นสาเหตุที่ฉันตั้งชื่อโพสต์ของฉันเช่นนี้ ในการเข้าใจย้อนหลังมันไม่ใช่วิธีที่ดีที่สุดในการอธิบายปัญหาฉันจะแก้ไขโพสต์ของฉัน
PythonNewb

1
ฉันคิดว่าคุณควรพิจารณาโพสต์โค้ดของคุณไปยัง codereview
Robert Harvey

1
เสียงเหมือนเครื่องจักรรัฐสำหรับฉัน object.state = object.function(object.state);
# # # # # # # # # # # # # # # # * * s โรเบิร์ต -Johnson

คำตอบทั้งหมดที่ให้ไว้จนถึงขณะนี้รวมถึงคำตอบที่ยอมรับได้พลาดไปด้วยเหตุผลหลักที่คำสั่งเปลี่ยนนั้นถือว่าไม่ดี พวกเขาไม่อนุญาตให้ปฏิบัติตามหลักการเปิด / ปิด
Dunk

คำตอบ:


13
  • ผมหันรถลงในเครื่องของรัฐทุกประเภทโดยใช้รูปแบบของรัฐ ไม่มีการประกาศ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

16

งบสวิตช์ไม่ดี

มันเป็นการทำให้เข้าใจง่ายเกินกว่าจะทำให้ชื่อโปรแกรมไม่ดี การใช้ifเป็นเพียง "ไม่ดี" เช่นเดียวกับการใช้คำสั่งเปลี่ยน ไม่ว่าด้วยวิธีการใดของคุณ

หากคุณต้องมีกฎที่เหมาะกับการกัดลองลองอันนี้:

คำสั่งเปลี่ยนกลายเป็นสิ่งที่เลวร้ายมากในขณะที่คุณมีสำเนาสองชุด

คำสั่ง switch ที่ไม่ซ้ำกันที่อื่นในฐานรหัสบางครั้งสามารถจัดการให้ไม่ชั่ว หากคดีไม่เป็นสาธารณะ แต่ถูกห่อหุ้มอยู่ธุรกิจของคนอื่นก็ไม่มีจริง โดยเฉพาะอย่างยิ่งถ้าคุณรู้วิธีและเวลาในการปรับโครงสร้างเป็นคลาส เพียงเพราะคุณไม่สามารถหมายความว่าคุณต้อง เป็นเพราะคุณสามารถที่จะทำสิ่งสำคัญน้อยกว่าตอนนี้

หากคุณพบว่าตัวเองพยายามที่จะผลักสิ่งต่าง ๆ เข้ามาในงบสวิตช์มากขึ้นการกระจายความรู้เกี่ยวกับคดีรอบตัวหรือคิดว่ามันไม่เลวร้ายนักที่จะทำสำเนาของมันออกมา

ถ้าคุณมีเวลาในการอ่านมากขึ้นกว่าเสียงกัดบางประการเกี่ยวกับ refactoring งบสวิตช์ c2 มีหน้ามีความสมดุลเป็นอย่างดีเกี่ยวกับกลิ่นงบเปลี่ยน

แม้แต่ในรหัส OOP สวิทช์ทุกตัวก็ไม่ดี มันเป็นวิธีที่คุณใช้มันและทำไม


2

รถยนต์เป็นเครื่องจักรประเภทรัฐ คำสั่ง Switch เป็นวิธีที่ง่ายที่สุดในการติดตั้งเครื่องที่ไม่มีสถานะสูงสุดและสถานะย่อย


2

งบสวิตช์ไม่เลว อย่าฟังคนที่พูดสิ่งต่าง ๆ เช่น "สวิทช์ที่ไม่ดี"! การใช้คำสั่ง switch โดยเฉพาะคือการใช้ antipattern เช่นการใช้ switch เพื่อเลียนแบบ subclassing (แต่คุณสามารถใช้ antipattern นี้กับ if's ได้ด้วยดังนั้นฉันเดาว่ามันแย่ด้วย!)

การใช้งานของคุณดูดี คุณถูกต้องจะรักษาได้ยากหากคุณเพิ่มสถานะอื่น ๆ อีกมากมาย แต่นี่ไม่ใช่เพียงปัญหาของการนำไปใช้งาน - การมีวัตถุที่มีหลายรัฐที่มีพฤติกรรมแตกต่างกันนั้นเป็นปัญหา การถ่ายภาพรถยนต์ของคุณมี 25 สถานะแต่ละการแสดงมีพฤติกรรมที่แตกต่างกันและกฎที่แตกต่างกันสำหรับการเปลี่ยนสถานะ เพียงระบุและบันทึกพฤติกรรมนี้จะเป็นงานที่ยิ่งใหญ่ คุณจะมีกฎการเปลี่ยนสถานะนับพัน ! ขนาดของswitchเพียงแค่จะเป็นอาการของปัญหาที่ใหญ่กว่า ดังนั้นถ้าเป็นไปได้หลีกเลี่ยงการลงไปตามถนนสายนี้

วิธีแก้ไขที่เป็นไปได้คือการแบ่งรัฐออกเป็นรัฐอิสระ ตัวอย่างเช่น REVERSE เป็นรัฐที่แตกต่างจาก DRIVE หรือไม่? บางทีสถานะรถอาจถูกแบ่งออกเป็นสองสถานะ: สถานะเครื่องยนต์ (ปิด, IDLE, DRIVE) และทิศทาง (ไปข้างหน้าย้อนกลับ) สถานะเครื่องยนต์และทิศทางอาจเป็นอิสระส่วนใหญ่ดังนั้นคุณจึงลดการทำซ้ำตรรกะและกฎการเปลี่ยนสถานะ วัตถุที่มีสถานะน้อยลงจะจัดการได้ง่ายกว่าวัตถุเดียวที่มีสถานะต่าง ๆ มากมาย


1

ในตัวอย่างของคุณรถยนต์เป็นเพียงเครื่องจักรกลในแง่วิทยาศาสตร์คอมพิวเตอร์คลาสสิค พวกเขามีชุดของรัฐขนาดเล็กที่กำหนดไว้อย่างดีและตรรกะการเปลี่ยนสถานะบางประเภท

ข้อเสนอแนะแรกของฉันคือพิจารณาแยกตรรกะการเปลี่ยนแปลงเป็นฟังก์ชันของตนเอง (หรือคลาสหากภาษาของคุณไม่รองรับฟังก์ชั่นชั้นหนึ่ง)

ข้อเสนอแนะที่สองของฉันคือพิจารณาแยกตรรกะการเปลี่ยนแปลงในสถานะซึ่งจะมีฟังก์ชันของตัวเอง (หรือคลาสหากภาษาของคุณไม่รองรับฟังก์ชั่นชั้นหนึ่ง)

ในทั้งสองรูปแบบกระบวนการสำหรับการเปลี่ยนสถานะจะมีลักษณะดังนี้:

mycar.transition()

หรือ

mycar.state.transition()

แน่นอนว่าเรื่องที่สองอาจจะถูกห่อหุ้มด้วยคลาสรถให้ดูเหมือนเป็นครั้งแรก

ในทั้งสองสถานการณ์การเพิ่มสถานะใหม่ (เช่นการเขียนแบบ) จะเกี่ยวข้องกับการเพิ่มประเภทสถานะวัตถุใหม่เท่านั้นและการเปลี่ยนวัตถุที่เปลี่ยนเป็นสถานะใหม่โดยเฉพาะ


0

มันขึ้นอยู่กับวิธีการที่มีขนาดใหญ่switchอาจจะมี

ในตัวอย่างของคุณฉันคิดว่า a switchก็โอเคเพราะไม่มีรัฐอื่นใดที่ฉันสามารถนึกได้ว่าคุณCarมีได้ดังนั้นมันจะไม่ใหญ่ขึ้นเมื่อเวลาผ่านไป

หากปัญหาเดียวที่มีสวิตช์ขนาดใหญ่ซึ่งแต่ละอันcaseมีคำแนะนำมากมายให้ทำวิธีการเฉพาะสำหรับแต่ละอัน

บางครั้งผู้คนแนะนำรูปแบบการออกแบบของรัฐแต่จะเหมาะสมกว่าเมื่อคุณจัดการกับตรรกะที่ซับซ้อนและรัฐต่าง ๆ ตัดสินใจทางธุรกิจที่แตกต่างกัน มิฉะนั้นปัญหาอย่างง่ายควรมีวิธีแก้ไขปัญหาที่ง่าย

ในบางสถานการณ์คุณสามารถมีวิธีการที่จะทำงานเฉพาะเมื่อสถานะเป็น A หรือ B แต่ไม่ใช่ C หรือ D หรือมีหลายวิธีที่มีการดำเนินการที่ง่ายมากที่ขึ้นอยู่กับสถานะ จากนั้นหนึ่งหรือหลายswitchงบจะดีกว่า


0

ฟังดูเหมือนเครื่องรัฐรุ่นเก่าที่ใช้ก่อนที่ใครจะเขียนโปรแกรม Object Oriented เพียงแค่รูปแบบการออกแบบ มันสามารถนำมาใช้ในภาษาใด ๆ ที่มีงบเปลี่ยนเช่น C.

อย่างที่คนอื่นพูดไม่มีอะไรผิดปกติกับคำสั่งสวิตช์ ทางเลือกมักซับซ้อนและเข้าใจยากกว่า

เว้นแต่ว่าจำนวนตัวสวิตช์จะใหญ่มากสิ่งนี้สามารถจัดการได้ค่อนข้างมาก ขั้นตอนแรกในการทำให้สามารถอ่านได้คือการแทนที่รหัสในแต่ละกรณีด้วยการเรียกใช้ฟังก์ชั่นเพื่อใช้พฤติกรรมของรัฐ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.