Implementasi status objek dalam bahasa OO?

11

Saya telah diberi beberapa kode Java untuk dilihat, yang mensimulasikan perlombaan mobil, yang mencakup implementasi mesin keadaan dasar. Ini bukan mesin negara ilmu komputer klasik, tetapi hanya sebuah objek yang dapat memiliki beberapa negara, dan dapat beralih di antara negara-negara berdasarkan serangkaian perhitungan.

Untuk menjelaskan masalahnya, saya punya kelas Mobil, dengan kelas enum bersarang yang mendefinisikan beberapa konstanta untuk kondisi Mobil (seperti OFF, IDLE, DRIVE, REVERSE, dll). Di dalam kelas Mobil yang sama ini saya memiliki fungsi pembaruan, yang pada dasarnya terdiri dari pernyataan sakelar besar yang mengaktifkan keadaan mobil saat ini, melakukan beberapa perhitungan dan kemudian mengubah keadaan mobil.

Sejauh yang saya bisa lihat, keadaan Cars hanya digunakan dalam kelasnya sendiri.

Pertanyaan saya adalah, apakah ini cara terbaik untuk berurusan dengan implementasi mesin negara yang dijelaskan di atas? Memang terdengar seperti solusi yang paling jelas, tetapi di masa lalu saya selalu mendengar bahwa "pernyataan switch buruk".

Masalah utama yang dapat saya lihat di sini adalah bahwa pernyataan switch mungkin menjadi sangat besar karena kami menambahkan lebih banyak status (jika dianggap perlu) dan kode dapat menjadi sulit digunakan dan sulit untuk dipelihara.

Apa solusi yang lebih baik untuk masalah ini?

PythonNewb
sumber
3
Deskripsi Anda tidak terdengar seperti mesin negara bagi saya; itu hanya terdengar seperti sekelompok benda mobil, masing-masing memiliki keadaan internal sendiri. Pertimbangkan memposting kode kerja Anda yang sebenarnya ke codereview.stackexchange.com ; orang-orang itu sangat pandai memberikan umpan balik pada kode kerja.
Robert Harvey
Mungkin "mesin negara" adalah pilihan kata yang buruk, tetapi ya, pada dasarnya kami memiliki banyak objek mobil yang beralih ke kondisi internal mereka sendiri. Sistem ini dapat dijelaskan dengan fasih dengan diagram status UML, itulah sebabnya saya memberi judul pada posting saya. Kalau dipikir-pikir, ini bukan cara terbaik untuk menggambarkan masalah ini, saya akan mengedit posting saya.
PythonNewb
1
Saya masih berpikir Anda harus mempertimbangkan memposting kode Anda ke codereview.
Robert Harvey
1
Kedengarannya seperti mesin negara bagi saya. object.state = object.function(object.state);
robert bristow-johnson
Semua jawaban yang diberikan sejauh ini, termasuk jawaban yang diterima kehilangan alasan utama bahwa pergantian pernyataan dianggap buruk. Mereka tidak mengizinkan kepatuhan pada prinsip terbuka / tertutup.
Dunk

Jawaban:

13
  • Saya mengubah mobil menjadi semacam mesin negara menggunakan Pola Negara . Perhatikan tidak ada switchatau if-then-elsepernyataan yang digunakan untuk pemilihan negara.

  • Dalam hal ini semua negara bagian adalah kelas dalam tetapi bisa diterapkan sebaliknya.

  • Setiap negara bagian berisi status valid yang dapat diubah.

  • Pengguna diminta untuk keadaan berikutnya jika lebih dari satu mungkin, atau hanya untuk mengonfirmasi jika hanya satu yang mungkin.

  • Anda dapat mengompilasinya dan menjalankannya untuk mengujinya.

  • Saya menggunakan kotak dialog grafik karena lebih mudah untuk menjalankannya secara interaktif di Eclipse.

masukkan deskripsi gambar di sini

Diagram UML diambil dari sini .

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();        
    }

}
Tulains Córdova
sumber
1
Saya sangat menyukai ini. Sementara saya menghargai jawaban teratas dan ini merupakan pembelaan dari pernyataan peralihan (saya akan selamanya mengingatnya sekarang), saya sangat menyukai ide pola ini. Terima kasih
PythonNewb
@PythonNewb Apakah Anda menjalankannya?
Tulains Córdova
Ya, itu bekerja dengan sempurna. Implementasinya akan sedikit berbeda untuk kode yang saya miliki, tetapi ide umumnya bagus. Saya pikir saya mungkin mempertimbangkan untuk memindahkan kelas-kelas negara bagian dari kelas penutup.
PythonNewb
1
@ PhythonNewb Saya mengubah kode ke versi yang lebih pendek menggunakan kembali perubahan negara / prompt untuk input logika menggunakan kelas abstrak, bukan antarmuka. Ini 20 baris lebih pendek tetapi saya diuji dan bekerja sama. Anda selalu bisa mendapatkan versi yang lebih lama dan lebih panjang dengan melihat riwayat edit.
Tulains Córdova
1
@Caleth Sebenarnya saya menulisnya seperti ini karena saya biasanya melakukannya dalam kehidupan nyata, yaitu, menyimpan potongan-potongan yang dapat dipertukarkan di peta dan membuatnya berdasarkan ID yang diambil dari file parameter. Biasanya yang saya simpan di peta bukanlah objek itu sendiri tetapi penciptanya jika benda itu mahal atau memiliki banyak keadaan non-statis.
Tulains Córdova
16

pernyataan switch buruk

Ini semacam penyederhanaan yang memberi pemrograman berorientasi objek nama yang buruk. Menggunakan ifsama buruknya dengan menggunakan pernyataan switch. Bagaimanapun Anda tidak mengirim secara polimorfik.

Jika Anda harus memiliki aturan yang sesuai dengan gigitan suara coba yang ini:

Pergantian pernyataan menjadi sangat buruk saat Anda memiliki dua salinannya.

Pernyataan beralih yang tidak digandakan di tempat lain dalam basis kode kadang-kadang dapat dikelola untuk tidak menjadi jahat. Jika kasusnya tidak umum, tetapi dikemas, itu benar-benar urusan orang lain. Terutama jika Anda tahu bagaimana dan kapan refactor ke dalam kelas. Hanya karena Anda tidak dapat berarti Anda harus melakukannya. Itu karena Anda dapat melakukannya sehingga kurang penting untuk melakukannya sekarang.

Jika Anda menemukan diri Anda mencoba untuk mendorong lebih banyak dan lebih banyak hal ke dalam pernyataan peralihan, menyebarkan pengetahuan tentang kasus-kasus di sekitar, atau berharap itu tidak begitu jahat untuk hanya membuat salinannya maka sudah waktunya untuk mengubah kasus ke dalam kelas yang terpisah.

Jika Anda punya waktu untuk membaca lebih dari beberapa gigitan suara tentang pernyataan penggantian refactoring c2 memiliki halaman yang sangat seimbang tentang bau pernyataan switch .

Bahkan dalam kode OOP, tidak setiap switch buruk. Begitulah cara Anda menggunakannya, dan mengapa.

candied_orange
sumber
2

Mobil adalah jenis mesin negara. Pernyataan peralihan adalah cara paling sederhana untuk mengimplementasikan mesin status yang tidak memiliki super state dan sub state.

Frank Hileman
sumber
2

Pergantian pernyataan tidak buruk. Jangan dengarkan orang yang mengatakan hal-hal seperti "alihkan statments itu buruk"! Beberapa penggunaan tertentu dari pernyataan switch adalah antipattern, seperti menggunakan switch untuk meniru subclassing. (Tetapi Anda juga dapat menerapkan antipattern ini dengan if, jadi saya kira jika buruk juga!).

Implementasi Anda terdengar bagus. Anda benar adalah akan sulit untuk mempertahankan jika Anda menambahkan lebih banyak negara. Tapi ini bukan hanya masalah implementasi - memiliki objek dengan banyak negara dengan perilaku berbeda itu sendiri merupakan masalah. Pencitraan mobil Anda memiliki 25 negara bagian yang masing-masing menunjukkan perilaku yang berbeda dan aturan yang berbeda untuk transisi negara. Hanya untuk menentukan dan mendokumentasikan perilaku ini akan menjadi tugas yang sangat besar. Anda akan memiliki ribuan aturan transisi negara! Ukuran switchhanya akan menjadi gejala dari masalah yang lebih besar. Jadi jika mungkin hindari jalan ini.

Obat yang mungkin adalah memecah negara menjadi substrat independen. Misalnya, apakah REVERSE benar-benar keadaan yang berbeda dari DRIVE? Mungkin status mobil dapat dipecah menjadi dua: Status engine (OFF, IDLE, DRIVE) dan arah (FORWARD, REVERSE). Status dan arah engine mungkin sebagian besar independen, sehingga Anda mengurangi duplikasi logika dan aturan transisi state. Lebih banyak objek dengan status lebih sedikit lebih mudah untuk dikelola daripada objek tunggal dengan banyak status.

JacquesB
sumber
1

Dalam contoh Anda, mobil hanyalah mesin negara dalam pengertian ilmu komputer klasik. Mereka memiliki sekelompok negara kecil yang didefinisikan dengan baik dan semacam logika transisi negara.

Saran pertama saya adalah mempertimbangkan memecah logika transisi ke fungsinya sendiri (atau kelas, jika bahasa Anda tidak mendukung fungsi kelas satu).

Saran kedua saya adalah untuk mempertimbangkan memecah logika transisi ke negara itu sendiri, yang akan memiliki fungsinya sendiri (atau kelas, jika bahasa Anda tidak mendukung fungsi kelas satu).

Dalam skema mana pun, proses untuk keadaan transisi akan terlihat seperti ini:

mycar.transition()

atau

mycar.state.transition()

Yang kedua, tentu saja, sepele bisa dibungkus dalam kelas mobil agar terlihat seperti yang pertama.

Dalam kedua skenario, menambahkan negara baru (katakanlah, DRAFT), hanya akan melibatkan menambahkan objek negara jenis baru dan mengubah objek yang secara khusus beralih ke keadaan baru.

Joel Harmon
sumber
0

Itu tergantung pada seberapa besar switchmungkin.

Dalam contoh Anda, saya pikir a switchtidak apa-apa karena sebenarnya tidak ada negara lain yang dapat saya pikirkan yang Cardapat Anda miliki, sehingga tidak akan bertambah besar seiring waktu.

Jika satu-satunya masalah adalah memiliki saklar besar di mana masing-masing casememiliki banyak instruksi, maka cukup buat metode pribadi yang berbeda untuk masing-masing.

Kadang-kadang orang menyarankan pola desain keadaan , tetapi lebih tepat ketika Anda berurusan dengan logika yang kompleks, dan menyatakan membuat keputusan bisnis yang berbeda untuk banyak operasi yang berbeda. Kalau tidak, masalah sederhana harus memiliki solusi sederhana.

Dalam beberapa skenario, Anda bisa memiliki metode yang hanya melakukan tugas ketika negara adalah A atau B, tetapi bukan C atau D, atau memiliki beberapa metode dengan operasi yang sangat sederhana yang bergantung pada negara. Maka satu atau beberapa switchpernyataan akan lebih baik.

Maxim Bernard
sumber
0

Ini kedengarannya seperti mesin state-school lama dari jenis yang digunakan sebelum siapa pun melakukan pemrograman Berorientasi Objek, apalagi Pola Desain. Ini dapat diimplementasikan dalam bahasa apa pun yang memiliki pernyataan peralihan, seperti C.

Seperti yang orang lain katakan, pada dasarnya tidak ada yang salah dengan pernyataan pergantian. Alternatifnya seringkali lebih rumit dan lebih sulit untuk dipahami.

Kecuali jika jumlah sakelar menjadi sangat besar, masalahnya tetap dapat dikelola. Langkah pertama agar tetap mudah dibaca adalah mengganti kode dalam setiap kasus dengan pemanggilan fungsi untuk mengimplementasikan perilaku negara.

Simon B
sumber