Dapatkah pola Strategi diimplementasikan tanpa percabangan yang signifikan?

14

Pola Strategi berfungsi dengan baik untuk menghindari besar jika ... konstruksi yang lain dan membuatnya lebih mudah untuk menambah atau mengganti fungsi. Namun, menurut saya masih ada satu kekurangan. Sepertinya dalam setiap implementasi masih perlu ada konstruksi percabangan. Mungkin pabrik atau file data. Sebagai contoh, ambil sistem pemesanan.

Pabrik:

// All of these classes implement OrderStrategy
switch (orderType) {
case NEW_ORDER: return new NewOrder();
case CANCELLATION: return new Cancellation();
case RETURN: return new Return();
}

Kode setelah ini tidak perlu khawatir, dan sekarang hanya ada satu tempat untuk menambahkan jenis pesanan baru, tetapi bagian kode ini masih tidak dapat diperluas. Menariknya ke dalam file data agak membantu keterbacaan (masih bisa diperdebatkan, saya tahu):

<strategies>
   <order type="NEW_ORDER">com.company.NewOrder</order>
   <order type="CANCELLATION">com.company.Cancellation</order>
   <order type="RETURN">com.company.Return</order>
</strategies>

Tapi ini masih menambahkan kode boilerplate untuk memproses file data - diberikan, lebih mudah diuji unit dan kode yang relatif stabil, tetapi kompleksitas tambahan nontheless.

Juga, konstruksi semacam ini tidak menguji integrasi dengan baik. Setiap strategi individu mungkin lebih mudah untuk diuji sekarang, tetapi setiap strategi baru yang Anda tambahkan adalah kompleksitas tambahan untuk diuji. Ini kurang dari yang Anda miliki jika Anda tidak menggunakan pola, tetapi masih ada.

Apakah ada cara untuk menerapkan pola strategi yang mengurangi kompleksitas ini? Atau apakah ini sesederhana mungkin, dan mencoba melangkah lebih jauh hanya akan menambah lapisan abstraksi untuk sedikit atau tanpa manfaat?

Michael K.
sumber
Hmmmmm .... mungkin untuk menyederhanakan hal-hal seperti eval... mungkin tidak berfungsi di Jawa tapi mungkin dalam bahasa lain?
FrustratedWithFormsDesigner
1
Refleksi @FrustratedWithFormsDesigner adalah kata ajaib di java
ratchet freak
2
Anda masih membutuhkan kondisi itu di suatu tempat. Dengan mendorong mereka ke pabrik, Anda cukup mematuhi KERING, karena jika tidak, pernyataan jika atau beralih mungkin muncul di beberapa tempat.
Daniel B
1
Cacat yang Anda sebutkan ini sering disebut pelanggaran terhadap prinsip terbuka-tertutup
k3b
jawaban yang diterima dalam pertanyaan terkait menyarankan kamus / peta sebagai alternatif untuk if-else dan beralih
gnat

Jawaban:

16

Tentu saja tidak. Bahkan jika Anda menggunakan wadah IoC, Anda harus memiliki kondisi di suatu tempat, memutuskan implementasi konkret yang akan disuntikkan. Ini adalah sifat dari pola Strategi.

Saya tidak benar-benar mengerti mengapa orang berpikir ini masalah. Ada pernyataan di beberapa buku, seperti Fowler's Refactoring , bahwa jika Anda melihat sakelar / kasing atau rantai if / elses di tengah kode lain, Anda harus mempertimbangkannya sebagai aroma dan ingin memindahkannya ke metodenya sendiri. Jika kode dalam setiap kasus lebih dari satu baris, mungkin dua, maka Anda harus mempertimbangkan menjadikan metode itu sebagai Metode Pabrik, mengembalikan Strategi.

Beberapa orang menganggap ini sebagai saklar / case buruk. Ini bukan kasusnya. Tapi itu harus berada pada dirinya sendiri, jika memungkinkan.

pdr
sumber
1
Saya juga tidak melihatnya sebagai hal yang buruk. Namun, saya selalu mencari cara untuk mengurangi jumlah kode yang akan dijamahnya, yang memunculkan pertanyaan.
Michael K
Beralih pernyataan (dan lama jika / else-if blok) buruk dan harus dihindari jika memungkinkan untuk menjaga kode Anda dapat dipertahankan. Yang mengatakan "jika mungkin" mengakui bahwa ada beberapa kasus di mana saklar harus ada, dan dalam kasus-kasus itu, cobalah untuk tetap turun ke satu tempat, dan satu yang membuatnya lebih sedikit mempertahankan tugas (sangat mudah untuk secara tidak sengaja kehilangan 1 dari 5 tempat dalam kode yang harus Anda selaraskan ketika Anda tidak mengisolasinya dengan benar).
Shadow Man
10

Dapatkah pola Strategi diimplementasikan tanpa percabangan yang signifikan?

Ya , dengan menggunakan hashmap / kamus tempat setiap implementasi Strategi mendaftar. Metode pabrik akan menjadi seperti itu

Class strategyType = allStrategies[orderType];
return runtime.create(strategyType);

Setiap implementasi strategi harus mendaftarkan pabrik dengan Tipe pemesanan dan beberapa informasi cara membuat kelas.

factory.register(NEW_ORDER, NewOrder.class);

Anda dapat menggunakan konstruktor statis untuk pendaftaran, jika bahasa Anda mendukung ini.

Metode register tidak lebih dari menambahkan nilai baru ke hashmap:

void register(OrderType orderType, Class class)
{
   allStrategies[orderType] = class;
}

[update 2012-05-04]
Solusi ini jauh lebih kompleks daripada "solusi peralihan" asli yang paling saya sukai.

Namun dalam lingkungan di mana strategi sering berubah (mis. Perhitungan harga tergantung pada pelanggan, waktu, ....) solusi hashmap ini dikombinasikan dengan IoC-Container bisa menjadi solusi yang baik.

k3b
sumber
3
Jadi sekarang Anda sudah memiliki seluruh Hashmap of Strategies, untuk menghindari peralihan / kasing? Bagaimana tepatnya baris factory.register(NEW_ORDER, NewOrder.class);pembersih, atau kurang pelanggaran OCP, daripada baris case NEW_ORDER: return new NewOrder();?
pdr
5
Sama sekali tidak bersih. Ini kontra-intuitif. Ini mengorbankan prinsip tetap-bodoh-bodoh-untuk meningkatkan prinsip terbuka-tertutup. Hal yang sama berlaku untuk inversi kontrol dan injeksi ketergantungan: ini lebih sulit untuk dipahami daripada solusi sederhana. Pertanyaannya adalah "implement ... tanpa percabangan yang signifikan" dan bukan "bagaimana membuat soluion yang lebih intuitif"
k3b
1
Saya tidak melihat bahwa Anda juga menghindari bercabang. Anda baru saja mengubah sintaks.
pdr
1
Apakah tidak ada masalah dengan solusi ini dalam bahasa yang tidak memuat kelas sampai dibutuhkan? Kode statis tidak akan berjalan sampai kelas dimuat, dan kelas tidak akan pernah dimuat karena tidak ada kelas lain yang merujuknya.
kevin cline
2
Secara pribadi saya suka metode ini, karena ini memungkinkan Anda untuk memperlakukan strategi Anda sebagai data yang bisa berubah , daripada memperlakukannya sebagai kode yang tidak dapat diubah.
Tacroy
2

"Strategi" adalah tentang harus memilih antara algoritma alternatif setidaknya sekali , tidak kurang. Di suatu tempat di program Anda seseorang harus membuat keputusan - mungkin pengguna atau program Anda. Jika Anda menggunakan IoC, refleksi, evaluator datafile atau konstruksi switch / kasus untuk menerapkan ini tidak mengubah situasi.

Doc Brown
sumber
Saya tidak setuju dengan pernyataan Anda sekali ini. Strategi dapat ditukar saat runtime. Misalnya, setelah gagal memproses permintaan menggunakan standar ProcessingStrategy, VerboseProcessingStrategy dapat dipilih dan pemrosesan dijalankan kembali.
dmux
@dmux: tentu, edit jawaban saya sesuai.
Doc Brown
1

Pola strategi digunakan ketika Anda menentukan perilaku yang mungkin dan paling baik digunakan ketika menunjuk pawang pada saat startup. Menentukan contoh strategi yang digunakan dapat dilakukan melalui Mediator, wadah IoC standar Anda, beberapa Pabrik seperti yang Anda gambarkan, atau hanya dengan menggunakan yang tepat berdasarkan konteks (karena seringkali, strategi tersebut dipasok sebagai bagian dari penggunaan yang lebih luas dari kelas yang memuatnya).

Untuk perilaku semacam ini di mana metode yang berbeda perlu dipanggil berdasarkan data, maka saya sarankan menggunakan konstruksi polimorfisme yang disediakan oleh bahasa yang ada; bukan pola strategi.

Telastyn
sumber
1

Dalam berbicara dengan pengembang lain tempat saya bekerja, saya menemukan solusi lain yang menarik - khusus untuk Java, tapi saya yakin idenya bekerja dalam bahasa lain. Buat enum (atau peta, tidak masalah yang mana) menggunakan referensi kelas dan instantiate menggunakan refleksi.

enum FactoryType {
   Type1(Type1.class),
   Type2(Type2.class);

   private Class<? extends Type> clazz;

   private FactoryType(Class<? extends Type> clazz) {
      this.clazz = clazz;
   }

   public Class<? extends Type> getTypeClass() {
      return clazz;
   }
}

Ini secara drastis mengurangi kode pabrik:

public Type create(FactoryType type) throws Exception {
   return type.getTypeClass().newInstance();
}

(Harap abaikan penanganan kesalahan yang buruk - contoh kode :))

Ini bukan yang paling fleksibel, karena build masih diperlukan ... tetapi mengurangi perubahan kode menjadi satu baris. Saya suka bahwa data dipisahkan dari kode pabrik. Anda bahkan dapat melakukan hal-hal seperti mengatur parameter dengan menggunakan kelas / antarmuka abstrak untuk menyediakan metode umum atau membuat anotasi waktu kompilasi untuk memaksa tanda tangan konstruktor tertentu.

Michael K.
sumber
Tidak yakin apa yang menyebabkan ini kembali ke beranda tetapi ini adalah jawaban yang bagus dan di Java 8 Anda sekarang dapat membuat referensi konstruktor tanpa refleksi.
JimmyJames
1

Ekstensibilitas dapat ditingkatkan dengan meminta setiap kelas menentukan jenis pesanannya sendiri. Lalu pabrik Anda memilih yang cocok.

Sebagai contoh:

public interface IOrderStrategy
{
    OrderType OrderType { get; }
}

public class NewOrder : IOrderStrategy
{
    public OrderType OrderType { get; } = OrderType.NewOrder;
}

public class OrderFactory
{
    private IEnumerable<IOrderStrategy> _strategies;

    public OrderFactory(IEnumerable<IOrderStrategy> strategies) // Injected by IoC container
    {
        _strategies = strategies;
    }

    public IOrderStrategy Create(OrderType orderType)
    {
        IOrderStrategy strategy = _strategies.FirstOrDefault(s => s.OrderType == orderType);

        if (strategy == null)
            throw new ArgumentException("Invalid order type.", nameof(orderType));

        return strategy;
    }
}
Eric Eskildsen
sumber
1

Saya pikir harus ada batasan pada berapa banyak cabang yang dapat diterima.

Misalnya, jika saya memiliki lebih dari delapan kasus di dalam pernyataan switch saya, maka saya mengevaluasi kembali kode saya dan mencari apa yang dapat saya faktor ulang. Cukup sering saya menemukan bahwa kasus-kasus tertentu dapat dikelompokkan menjadi pabrik yang terpisah. Asumsi saya di sini adalah ada pabrik yang membangun strategi.

Bagaimanapun, Anda tidak dapat menghindari ini dan di suatu tempat Anda harus memeriksa keadaan atau jenis objek yang Anda kerjakan. Anda kemudian akan membangun strategi untuk itu. Pemisahan keprihatinan yang lama.

CodeART
sumber