Birden fazla anahtar durumlu bir uygulama nasıl yeniden düzenlenir?


10

Ben girdi olarak bir tamsayı alır ve girişe dayalı farklı sınıfların statik yöntemleri çağırır bir uygulama var. Her yeni sayı eklendiğinde, başka bir vaka eklememiz ve farklı bir sınıfın farklı bir statik yöntemini çağırmamız gerekir. Artık anahtarda 50 kasa var ve her seferinde başka bir kasa eklemem gerektiğinde ürperiyorum. Bunu yapmanın daha iyi bir yolu var mı?

Biraz düşündüm ve bu fikri buldum. Strateji modelini kullanıyorum. Bir anahtar durumu yerine, anahtar girdi tamsayı olan strateji nesneleri bir harita var. Yöntem çağrıldıktan sonra, nesneyi arar ve nesne için genel yöntemi çağırır. Bu şekilde anahtar kutusu yapısını kullanmaktan kaçınabilirim.

Ne düşünüyorsun?


2
Mevcut kodla ilgili asıl sorun nedir?
Philip Kendall

Bu değişikliklerden birini yapmanız gerektiğinde ne olur? switchKarmaşık sisteminizde bir vaka eklemeniz ve önceden var olan bir yöntemi çağırmanız mı gerekiyor, yoksa hem yöntemi hem de çağrısını icat etmeniz mi gerekiyor?
Kilian Foth

@KilianFoth Bu projeyi bir bakım geliştiricisi olarak devraldım ve henüz herhangi bir değişiklik yapmak zorunda kalmadım. Ancak yakında değişiklikler yapacağım, bu yüzden şimdi yeniden düzenleme yapmak istiyorum. Ama sorunuzu cevaplamak için, ikincisine evet.
Kaushik Chakraborty

2
Sanırım neler olduğuna dair yoğun bir örnek göstermeniz gerekiyor.
whatsisname

1
@KaushikChakraborty: sonra bellekten bir örnek oluşturun. 250'den fazla uber anahtarının uygun olduğu durumlar vardır ve ne kadar az durumda olursa olsun anahtarın kötü olduğu durumlar vardır. Şeytan ayrıntılarda ve hiçbir ayrıntımız yok.
whatsisname

Yanıtlar:


13

Artık anahtarda 50 kasa var ve her seferinde başka bir kasa eklemem gerektiğinde ürperiyorum.

Ben polimorfizmi seviyorum. SOLID'i seviyorum. Saf nesne yönelimli programlamayı seviyorum. Bunların kötü bir temsilci olduğunu görmekten nefret ediyorum çünkü dogmatik olarak başvuruyorlar.

Stratejiyi yeniden düzenlemek için iyi bir örnek vermediniz. Yeniden düzenleme işleminin bu arada bir adı vardır. Buna Koşulluları Polimorfizmle Değiştir denir .

C2.com'dan sizin için uygun bir tavsiye buldum :

Sadece aynı veya çok benzer koşullu testlerin sık sık tekrarlanması gerçekten mantıklıdır. Basit, nadiren tekrarlanan testler için, basit bir koşulu birden çok sınıf tanımının ayrıntılarıyla değiştirmek ve büyük olasılıkla bunları aslında şartlı olarak gerekli aktiviteyi gerektiren koddan uzağa taşımak, kod kitaplığının bir ders kitabı örneğiyle sonuçlanacaktır. Dogmatik saflığa göre netliği tercih edin. - DanMuller

50 kasalı bir anahtarınız var ve alternatifiniz 50 nesne üretmektir. Oh ve 50 satır nesne inşaat kodu. Bu ilerleme değil. Neden olmasın? Çünkü bu yeniden düzenleme sayı 50'den düşürmek için hiçbir şey yapmaz. Bu yeniden düzenleme kullanarak başka bir yerde aynı girdi üzerinde başka bir anahtar deyimi oluşturmanız gerekir. İşte bu yeniden düzenleme, 100'ü 50'ye çevirdiği için yardımcı oluyor.

Sahip olduğunuz tek anahtar gibi "anahtara" atıfta bulunduğunuz sürece bunu önermiyorum. Şimdi yeniden düzenleme yapmanın tek avantajı, bazı goofball'un 50 kasa anahtarınızı kopyalayıp yapıştırma şansını azaltmasıdır.

Önerdiğim, bu 50 vakayı, çarpanlarına ayrılabilecek ortaklıklar açısından yakından incelemek. Yani 50 mi? Gerçekten mi? Bu kadar çok davaya ihtiyacınız olduğundan emin misiniz? Burada çok şey yapmaya çalışıyor olabilirsiniz.


Söylediklerine katılıyorum. Kodun fazlalık fazlalığı var, birçok vaka bile gerekli değil, ama bir cursory bakışından öyle görünmüyor olabilir. Vakaların her biri, birden fazla sistemi çağıran ve sonuçları toplayan ve çağrı koduna geri dönen bir yöntemi çağırır. Her sınıf kendi içinde bulunur, bir iş yapar ve korkarım ki vaka sayısını azaltmak için yüksek uyum ilkesini ihlal edeceğim.
Kaushik Chakraborty

3
Yüksek uyumu ihlal etmeden 50 tane alabilirim ve her şeyi kendine saklayabilirim. Sadece bir numarayla yapamam. 2, 5 ve 5'e ihtiyacım var. Bu yüzden çarpanlara ayırma denir. Cidden, tüm mimarinize bakın ve içinde bulunduğunuz bu 50 cehennemden bir çıkış yolu görüp göremeyeceğinize bakın. Yeniden düzenleme kötü kararları geri almakla ilgilidir. Onları yeni biçimlerde sürdürmemek.
candied_orange

Şimdi, bu yeniden düzenleme kullanarak 50 azaltmak için bir yol görebiliyorsanız, bunun için gidin. Doc Browns fikrinden yararlanmak için: Bir harita haritası iki anahtar alabilir. Düşünmek için bir şey.
candied_orange

1
Candied'in yorumuna katılıyorum. Sorun, switch ifadesindeki 50 durum değil, sorun, 50 seçenek arasında karar vermesi gereken bir işlevi çağırmanıza neden olan daha yüksek düzey mimari tasarımdır. Bazı çok büyük ve karmaşık sistemler tasarladım ve hiç böyle bir duruma zorlanmadım.
Dunk

@Candied "Bu yeniden düzenleme işlemini, aynı girdide başka bir yerde başka bir anahtar ifadesi oluşturmanız gerektiğini bulduğunuzda kullanırsınız." Bunu ayrıntılı olarak açıklayabilir misiniz? Anahtar durumlarım olduğu ancak farklı katmanlarda olduğu gibi benzer bir durumum var. proje ilk yetkilendirme, onaylama, CRUD prosedürleri sonra dao. Yani her katmanda, aynı girdi üzerinde bir tamsayı olan, ancak auth, geçerli gibi farklı işlevler yapan anahtar durumlar vardır. bu yüzden her biri farklı yöntemlere sahip bir sınıf köknar oluşturmalıyız? Bizim durumumuz, “aynı girişte aynı anahtarı tekrarlayarak” söylemeye çalıştığınız şeye uyuyor mu?
Siddharth Trikha

9

Kodunuzun bazı işlevlerinde başlatılan ve aşağıdaki gibi birkaç kod satırına sahip olduğunuz strateji nesnelerinin tek başına haritası

     myMap.Add(1,new Strategy1());
     myMap.Add(2,new Strategy2());
     myMap.Add(3,new Strategy3());

siz ve meslektaşlarınızın ayrı sınıflarda çağrılacak işlevleri / stratejileri daha düzenli bir şekilde uygulamalarını gerektirir (çünkü strateji nesnelerinizin aynı arabirimi uygulaması gerekir). Bu kod genellikle biraz daha kapsamlıdır.

     case 1:
          MyClass1.Doit1(someParameters);
          break;
     case 2:
          MyClass2.Doit2(someParameters);
          break;
     case 3:
          MyClass3.Doit3(someParameters);
          break;

Ancak, yeni bir numara eklenmesi gerektiğinde sizi bu kod dosyasını düzenleme yükünden kurtarmaz. Bu yaklaşımın gerçek faydaları farklıdır:

  • Haritanın başlatma şimdi aslında gönderme kodu ayrılmış olur çağıran belirli bir sayıya bağlı fonksiyon ve ikincisi yok değil artık bu 50 tekrara içerirler, bu sadece benzeyecek myMap[number].DoIt(someParameters). Bu nedenle, yeni bir numara geldiğinde bu dağıtım koduna dokunulmasına gerek yoktur ve Açık-Kapalı prensibine göre uygulanabilir. Dahası, dağıtım kodunun kendisini genişletmeniz gereken gereksinimler olduğunda, artık 50 yeri değiştirmek zorunda kalmayacaksınız, sadece bir tanesini değiştirmek zorunda kalacaksınız.

  • haritanın içeriği çalışma zamanında belirlenir (anahtar yapısının içeriği derleme saatinden önce belirlenirken), bu size başlatma mantığını daha esnek veya genişletilebilir yapma fırsatı verir.

Yani evet, bazı faydaları var ve bu kesinlikle daha fazla SOLID koduna doğru bir adım. Ancak, refactor'a ödüyorsa, sizin veya ekibinizin kendi başına karar vermesi gereken bir şeydir. Gönderim kodunun değiştirilmesini beklemiyorsanız, başlatma mantığının değiştirilmesini ve okunabilirliğinin switchgerçek bir sorun olmadığını düşünüyorsanız, yeniden düzenleme işleminiz o kadar önemli olmayabilir.


Her anahtarı körü körüne polimorfizmle değiştirmeye isteksizken, Doc Brown'un burada önerdiği yolun geçmişte benim için çok iyi çalıştığını söyleyeceğim. Aynı arabirimini uygulamak yerine lütfen Doit1, Doit2bir vb, Doitbirçok farklı uygulamaları vardır yöntemle.
candied_orange

Ve anahtar olarak kullanılan giriş sembolünün türü üzerinde kontrolünüz varsa, giriş sembolünün doTheThing()bir yöntemini yaparak bir adım daha ileri gidebilirsiniz . O zaman haritayı korumanıza gerek kalmaz.
Kevin Krumwiede

1
@KevinKrumwiede: önerdiğiniz şey, tamsayıların yerine strateji nesnelerini programın içinden geçirmek anlamına gelir. Ancak, program bir dış veri kaynağından girdi olarak bir tamsayı aldığında, tamsayıdan ilgili stratejiye sistemin en az bir yerinde eşleme yapılmalıdır .
Doc Brown

Doc Brown'un önerisine genişleme: bu şekilde gitmeye karar verirseniz, strateji nesnelerini oluşturma mantığını içerecek bir fabrika da oluşturabilirsiniz. Bununla birlikte, CandiedOrange tarafından verilen cevap bana çok mantıklı geliyor.
Vladimir Stokic

@DocBrown "Giriş sembolünün türü üzerinde kontrolünüz varsa" işte buna başlamıştım.
Kevin Krumwiede

0

@DocBrown'un cevabında ana hatlarıyla verilen strateji lehine çok iyiyim .

Cevaba bir iyileştirme önereceğim.

Aramalar

 myMap.Add(1,new Strategy1());
 myMap.Add(2,new Strategy2());
 myMap.Add(3,new Strategy3());

dağıtılabilir. Açık-Kapalı prensibine daha iyi uyan başka bir strateji eklemek için aynı dosyaya geri dönmek zorunda değilsiniz.

Strategy1Strategy1.cpp dosyasında uyguladığınızı varsayalım. İçinde aşağıdaki kod bloğuna sahip olabilirsiniz.

namespace Strategy1_Impl
{
   struct Initializer
   {
      Initializer()
      {
         getMap().Add(1, new Strategy1());
      }
   };
}
using namespace Strategy1_Impl;

static Initializer initializer;

Her StategyN.cpp dosyasında aynı kodu tekrarlayabilirsiniz. Gördüğünüz gibi, bu tekrarlanan kod çok olacak. Kod çoğaltmayı azaltmak için, tüm Strategysınıfların erişebileceği bir dosyaya yerleştirilebilecek bir şablon kullanabilirsiniz .

namespace StrategyHelper
{
   template <int N, typename StrategyType> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new StrategyType());
      }
   };
}

Bundan sonra, Strategy1.cpp'de kullanmanız gereken tek şey:

static StrategyHelper::Initializer<1, Strategy1> initializer;

StrategyN.cpp dosyasında ilgili satır:

static StrategyHelper::Initializer<N, StrategyN> initializer;

Somut Strateji sınıfları için bir sınıf şablonu kullanarak şablonların kullanımını başka bir seviyeye taşıyabilirsiniz.

class Strategy { ... };

template <int N> class ConcreteStrategy;

Ve sonra, yerine Strategy1kullanın ConcreteStrategy<1>.

template <> class ConcreteStrategy<1> : public Strategy { ... };

S'yi kaydetmek için yardımcı sınıfı değiştirin Strategy:

namespace StrategyHelper
{
   template <int N> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new ConcreteStrategy<N>());
      }
   };
}

Strateg1.cpp içindeki kodu şu şekilde değiştirin:

static StrategyHelper::Initializer<1> initializer;

StrategN.cpp'deki kodu şu şekilde değiştirin:

static StrategyHelper::Initializer<N> initializer;
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.