Strateji modeli önemli bir dallanma olmadan uygulanabilir mi?


14

Strateji kalıbı, eğer başka bir şey yapılandırırsa ve işlevsellik eklemeyi veya değiştirmeyi kolaylaştırırsa çok büyük önlemek için iyi çalışır. Ancak bence hala bir kusur var. Her uygulamada hala bir dallanma yapısı olması gerektiği anlaşılıyor. Bir fabrika veya veri dosyası olabilir. Örnek olarak bir sipariş sistemini ele alalım.

Fabrika:

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

Bundan sonraki kodun endişelenmesine gerek yoktur ve şimdi yeni bir sipariş türü eklemek için yalnızca bir yer vardır, ancak kodun bu bölümü hala genişletilemez. Bir veri dosyasına çekilmesi biraz okunabilirliğe yardımcı olur (tartışmalı, biliyorum):

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

Ancak bu yine de veri dosyasını işlemek için kaynak plakası ekler - verilen, daha kolay birim test edilebilir ve nispeten kararlı kod, ancak yine de ek karmaşıklık.

Ayrıca, bu tür bir yapı entegrasyon testini iyi yapmaz. Her bir stratejinin test edilmesi artık daha kolay olabilir, ancak eklediğiniz her yeni stratejinin test edilmesi için ek karmaşıklık vardır. Bu eğer olurdu bile az olmasaydı deseni kullanılmış, ama hala orada.

Bu karmaşıklığı azaltan strateji modelini uygulamanın bir yolu var mı? Yoksa bu kadar basit ve daha ileri gitmeye çalışmak, çok az veya hiç fayda için sadece bir soyutlama katmanı ekler mi?


Hmmmmm .... evalJava ile çalışmayabilir, belki diğer dillerde olabilir gibi şeylerle işleri basitleştirmek mümkün olabilir?
SinirliWithFormsDesigner

1
@FrustratedWithFormsDesigner yansıması java'daki sihirli kelimedir
cırcır ucube

2
Bu koşullara hala bir yerde ihtiyacınız olacak. Onları bir fabrikaya göndererek, DRY'ye uyuyorsunuz, aksi takdirde if veya switch ifadesi birden fazla yerde görünmüş olabilir.
Daniel B

1
Bahsettiğiniz kusur genellikle açık-kapalı-prensibinin ihlali olarak
k3b

ilgili sorudaki kabul edilen cevap, if-else ve switch'e alternatif olarak sözlük / haritayı önermektedir
gnat

Yanıtlar:


16

Tabii ki değil. Bir IoC kabı kullansanız bile, hangi somut uygulamanın enjekte edileceğine karar vererek bir yerlerde koşullara sahip olmanız gerekir. Strateji modelinin doğası budur.

İnsanların bunun neden bir sorun olduğunu düşündüklerini gerçekten anlamıyorum. Bazı kitaplarda, Fowler's Refactoring gibi , başka bir kodun ortasında bir anahtar / durum veya if / elses zinciri görürseniz, bir kokuyu düşünmeli ve kendi yöntemine taşımak için bakmalısınız. Her durumdaki kod bir satırdan, belki iki satırdan fazlaysa, bu yöntemi Stratejiler döndürerek bir Fabrika Yöntemi yapmayı düşünmelisiniz.

Bazı insanlar bunu bir anahtarın / davanın kötü olduğu anlamına geldi. Durum bu değil. Ancak mümkünse kendi başına kalmalıdır.


1
Ben de kötü bir şey olarak görmüyorum. Ancak, ben her zaman soruyu soracaktır korumak, korumak kod miktarını azaltmak için yollar arıyorum.
Michael K

Anahtar ifadeleri (ve if / else-if blokları uzunsa) kötüdür ve kodunuzun bakımını yapmak için mümkün olduğunca kaçınılmalıdır. Bununla birlikte, "mümkünse" , anahtarın olması gereken bazı durumlar olduğunu kabul eder ve bu durumlarda, tek bir yerde tutmaya çalışın ve onu daha az angarya yapmayı sağlayan bir yer ( yanlışlıkla doğru şekilde ayırmadığınızda senkronize kalmanız için gereken 5 yerden 1 tanesini yanlışlıkla kaçırmayın).
Gölge Adam

10

Strateji modeli önemli bir dallanma olmadan uygulanabilir mi?

Evet , her Strateji uygulamasının kayıtlı olduğu bir hashmap / sözlük kullanarak. Fabrika yöntemi olacak gibi bir şey

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

Her strateji uygulaması, fabrikaya orderType ve sınıfın nasıl oluşturulacağı hakkında bazı bilgiler kaydettirmelidir.

factory.register(NEW_ORDER, NewOrder.class);

Diliniz destekliyorsa, kayıt için statik yapıcıyı kullanabilirsiniz.

Register yöntemi, hashmap öğesine yeni bir değer eklemekten başka bir şey yapmaz:

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

[update 2012-05-04]
Bu çözüm, çoğu zaman tercih ediyorum orijinal "anahtar çözüm" çok daha karmaşıktır.

Bununla birlikte, stratejilerin sık sık değiştiği bir ortamda (örneğin, müşteriye, zamana bağlı olarak fiyat hesaplaması ...) bir IoC-Container ile birleştirilmiş bu hashmap çözümü iyi bir çözüm olabilir.


3
Yani artık bir anahtar / davadan kaçınmak için tam bir Strateji Hashmap'ınız var mı? factory.register(NEW_ORDER, NewOrder.class);Sıradan daha temiz sıralar veya daha az OCP ihlali case NEW_ORDER: return new NewOrder();nedir?
pdr

5
Hiç temiz değil. Karşı sezgiseldir. Açık-kapalı-prensibini iyileştirmek için devam et-aptal-aptal prensibi feda eder. Aynısı kontrolün tersine çevrilmesi ve bağımlılık enjeksiyonuna da uygulanır: bunların anlaşılması basit bir çözümden daha zordur. Soru "daha sezgisel bir çözümün nasıl yaratılacağı değil," önemli bir dallanma olmadan uygula "idi
k3b

1
Ben de dallanmayı önlediğinizi görmüyorum. Sözdizimini yeni değiştirdiniz.
pdr

1
Bu çözümde, sınıflar gerekmedikçe yüklenmeyen dillerde bir sorun yok mu? Statik kod, sınıf yükleninceye kadar çalışmaz ve başka hiçbir sınıf buna başvurmadığı için sınıf hiçbir zaman yüklenmez.
kevin cline

2
Şahsen bu yöntemi seviyorum, çünkü stratejilerinizi değişmez kod olarak ele almak yerine değiştirilebilir veriler olarak ele almanıza izin veriyor .
Tacroy

2

"Strateji" alternatif algoritmalar arasında en az bir kez , daha az değil, seçim yapmakla ilgilidir. Programınızın bir yerinde birisinin bir karar vermesi gerekir - belki kullanıcı veya programınız. Bunu uygulamak için bir IoC, yansıma, bir veri dosyası değerlendiricisi veya bir anahtar / vaka yapısı kullanırsanız durum değişmez.


Bir kereliğin ifadesine katılmıyorum . Stratejiler çalışma zamanında değiştirilebilir. Örneğin, bir talebi standart bir ProcessingStrategy kullanarak işleyemedikten sonra bir VerboseProcessingStrategy seçilebilir ve işleme yeniden başlatılabilir.
dmux

@dmux: eminim cevabımı buna göre düzenledim.
Doc Brown

1

Strateji deseni olası davranışları belirlerken kullanılır ve en iyi başlangıçta bir işleyici belirtilirken kullanılır. Kullanılacak stratejinin hangi örneğinin bir Arabulucu, standart IoC konteyneriniz, tanımladığınız gibi bazı Fabrikalar aracılığıyla veya yalnızca içeriğe dayalı olarak doğru olanı kullanarak (genellikle daha geniş bir kullanımın parçası olarak strateji sağlandığı için) yapılabileceğini belirtme içeren sınıfın).

Verilere dayalı olarak farklı yöntemlerin çağrılması gereken bu tür davranışlar için, eldeki dil tarafından sağlanan polimorfizm yapılarını kullanmanızı öneririm; strateji kalıbı değil.


1

Çalıştığım diğer geliştiricilerle konuşurken, Java'ya özgü başka bir ilginç çözüm buldum, ancak fikrin diğer dillerde çalıştığından eminim. Sınıf referanslarını kullanarak bir enum (veya bir harita, çok fazla önemli değil) oluşturun ve yansıma kullanarak başlatın.

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

Bu, fabrika kodunu büyük ölçüde azaltır:

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

(Lütfen kötü hata işlemeyi dikkate almayın - örnek kod :))

Bir yapı hala gerekli olduğundan en esnek değil ... ama kod değişikliğini bir satıra indiriyor. Verilerin fabrika kodundan ayrılmasını seviyorum. Hatta ortak bir yöntem sağlamak için soyut sınıflar / arabirimler kullanarak veya belirli kurucu imzalarını zorlamak için derleme zamanı ek açıklamaları oluşturarak parametreleri ayarlamak gibi şeyler yapabilirsiniz.


Bunun ana sayfaya geri dönmesine neden olan şeyden emin değilim, ancak bu iyi bir yanıttır ve Java 8'de şimdi yansıtıcı olmadan Oluşturucu referansları oluşturabilirsiniz.
JimmyJames

1

Genişletilebilirlik, her sınıfın kendi sipariş türünü tanımlamasıyla geliştirilebilir. Ardından fabrikanız uygun olanı seçer.

Örneğin:

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

1

Bence kaç şubenin kabul edilebilir olduğuna dair bir sınır olmalı.

Örneğin, benim anahtar deyim içinde sekizden fazla vaka varsa, o zaman kodumu yeniden değerlendirmek ve ben yeniden faktör olabilir ne ararım. Çoğu zaman belirli vakaların ayrı bir fabrikada gruplandırılabileceğini görüyorum. Buradaki varsayımım, stratejiler inşa eden bir fabrika olduğu.

Her iki durumda da, bundan kaçınamazsınız ve birlikte çalıştığınız nesnenin durumunu veya türünü kontrol etmeniz gerekir. Daha sonra bunun için bir strateji oluşturacaksınız. Endişelerin eski iyi ayrılığı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.