Kodda anahtar kullanımını ortadan kaldırmanın yolları nelerdir?
Kodda anahtar kullanımını ortadan kaldırmanın yolları nelerdir?
Yanıtlar:
Anahtar ifadeleri kendi başına bir antipattern değildir, ancak nesne yönelimli kodlama yapıyorsanız, bir anahtar kullanımının bir anahtar ifadesi kullanmak yerine polimorfizm ile daha iyi çözülüp çözülmediğini düşünmelisiniz .
Polimorfizm ile bu:
foreach (var animal in zoo) {
switch (typeof(animal)) {
case "dog":
echo animal.bark();
break;
case "cat":
echo animal.meow();
break;
}
}
bu olur:
foreach (var animal in zoo) {
echo animal.speak();
}
typeof
ve bu cevap diğer durumlarda switch deyimleri üzerinde çalışmanın yollarını veya nedenlerini önermiyor.
Bkz. Anahtar İfadeleri Kokusu :
Tipik olarak, benzer anahtar ifadeleri bir program boyunca dağılmıştır. Bir anahtarda bir cümle ekler veya kaldırırsanız, genellikle diğerlerini de bulup onarmanız gerekir.
Hem Refactoring ve Patterns için Refactoring bu sorunu çözmek için yaklaşımlar var.
(Sahte) kodunuz aşağıdaki gibi görünüyorsa:
class RequestHandler {
public void handleRequest(int action) {
switch(action) {
case LOGIN:
doLogin();
break;
case LOGOUT:
doLogout();
break;
case QUERY:
doQuery();
break;
}
}
}
Bu kod Açık Kapalı Prensibi'ni ihlal eder ve ortaya çıkan her yeni eylem kodu türü için kırılgandır. Bunu düzeltmek için bir 'Komut' nesnesi getirebilirsiniz:
interface Command {
public void execute();
}
class LoginCommand implements Command {
public void execute() {
// do what doLogin() used to do
}
}
class RequestHandler {
private Map<Integer, Command> commandMap; // injected in, or obtained from a factory
public void handleRequest(int action) {
Command command = commandMap.get(action);
command.execute();
}
}
(Sahte) kodunuz aşağıdaki gibi görünüyorsa:
class House {
private int state;
public void enter() {
switch (state) {
case INSIDE:
throw new Exception("Cannot enter. Already inside");
case OUTSIDE:
state = INSIDE;
...
break;
}
}
public void exit() {
switch (state) {
case INSIDE:
state = OUTSIDE;
...
break;
case OUTSIDE:
throw new Exception("Cannot leave. Already outside");
}
}
Sonra bir 'Devlet' nesnesi getirebilirsiniz.
// Throw exceptions unless the behavior is overriden by subclasses
abstract class HouseState {
public HouseState enter() {
throw new Exception("Cannot enter");
}
public HouseState leave() {
throw new Exception("Cannot leave");
}
}
class Inside extends HouseState {
public HouseState leave() {
return new Outside();
}
}
class Outside extends HouseState {
public HouseState enter() {
return new Inside();
}
}
class House {
private HouseState state;
public void enter() {
this.state = this.state.enter();
}
public void leave() {
this.state = this.state.leave();
}
}
Bu yardımcı olur umarım.
Map<Integer, Command>
bir anahtara ihtiyacı olmaz mı?
Anahtar, bir anahtar deyimiyle uygulanıp uygulanmadığına bakılmaksızın, başka bir zincir, arama tablosu, oop polimorfizmi, kalıp eşleştirme veya başka bir şeydir.
" Anahtar ifadesi " veya " anahtar düzeni " kullanımını ortadan kaldırmak istiyor musunuz ? Birincisi, ikincisi, yalnızca başka bir desen / algoritma kullanılabiliyorsa ve çoğu zaman mümkün olmayan veya bunu yapmak daha iyi bir yaklaşım değilse, ortadan kaldırılabilir.
Switch deyimini koddan kaldırmak istiyorsanız , sorulması gereken ilk soru , switch deyimini ortadan kaldırmanın ve başka bir teknik kullanmanın nerede mantıklı olduğudur. Ne yazık ki bu sorunun cevabı alan adına özgüdür.
Ve derleyicilerin deyimleri değiştirmek için çeşitli optimizasyonlar yapabileceğini unutmayın. Örneğin, mesaj işlemeyi verimli bir şekilde yapmak istiyorsanız, bir switch deyimi hemen hemen gitmenin yoludur. Ancak öte yandan, bir switch deyimine dayalı iş kurallarını çalıştırmak muhtemelen en iyi yol değildir ve uygulama yeniden araştırılmalıdır.
Şunu değiştirmek için bazı alternatifler:
Anahtarın kendisi o kadar da kötü değildir, ancak yöntemlerinizdeki nesnelerde çok fazla "anahtar" veya "if / else" varsa, tasarımınızın biraz "yordamsal" olduğuna ve nesnelerinizin sadece değer olduğuna dair bir işaret olabilir. kovalar. Mantığı nesnelerinize taşıyın, nesnelerinize bir yöntem çağırın ve bunun yerine nasıl yanıt vereceğine karar vermelerine izin verin.
Bence en iyi yol iyi bir harita kullanmaktır. Bir sözlük kullanarak hemen hemen her girdiyi başka bir değer / nesne / işlevle eşleyebilirsiniz.
kodunuz şöyle bir şey (psuedo) olurdu:
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
Herkes BÜYÜK if else
blokları sever . Okuması çok kolay! Yine de neden switch ifadelerini kaldırmak istediğinizi merak ediyorum. Bir switch deyimine ihtiyacınız varsa, muhtemelen bir switch deyimine ihtiyacınız vardır. Cidden olsa, kodun ne yaptığına bağlı olduğunu söyleyebilirim. Anahtarın yaptığı tüm işlevler (diyelim ki) işlevleri çağırıyorsa, işlev işaretçileri iletebilirsiniz. O olsun bir daha çözüm tartışmalıdır.
Dil burada da önemli bir faktör.
Aradığın şeyin Strateji Örneği olduğunu düşünüyorum.
Bu, bu sorunun diğer cevaplarında belirtilen çeşitli şekillerde uygulanabilir, örneğin:
switch
ifadelere yeni durumlar veya yeni davranışlar eklediğinizi fark ederseniz, ifadeleri değiştirmek iyi olur:
int durumu; Dize getString () { anahtar (durum) { case 0: // durum 0 için davranış dönüş "sıfır"; durum 1: // durum 1 için davranış dönüş "bir"; } yeni IllegalStateException () öğesi ata; } çift getDouble () { switch (this.state) { case 0: // durum 0 için davranış dönüş 0d; durum 1: // durum 1 için davranış dönüş 1d; } yeni IllegalStateException () öğesi ata; }
Yeni davranış eklemek, kopyalamanın switch
ve yeni durumların eklenmesi case
, her switch
ifadeye bir başkasının eklenmesi anlamına gelir .
Java'da, yalnızca çalışma zamanında değerlerini bildiğiniz çok sınırlı sayıda ilkel tür arasında geçiş yapabilirsiniz. Bu kendi başına bir sorun yaratır: durumlar sihirli sayılar veya karakterler olarak temsil edilir.
Örüntü eşleştirme ve birden çok if - else
blok kullanılabilir, ancak yeni davranışlar ve yeni durumlar eklerken gerçekten aynı sorunlara sahiptir.
Başkalarının "polimorfizm" olarak önerdiği çözüm, Devlet modelinin bir örneğidir :
Eyaletlerin her birini kendi sınıfıyla değiştirin. Her davranışın sınıfta kendi yöntemi vardır:
IState durumu; Dize getString () { return state.getString (); } çift getDouble () { return state.getDouble (); }
Her yeni durum eklediğinizde, IState
arayüzün yeni bir uygulamasını eklemeniz gerekir . Bir switch
dünyada, case
her birine bir eklersiniz switch
.
Her yeni davranış eklediğinizde, IState
arabirime ve uygulamaların her birine yeni bir yöntem eklemeniz gerekir . Bu, öncekiyle aynı yüktür, ancak şimdi derleyici, önceden var olan her durum üzerinde yeni davranış uygulamalarınız olup olmadığını kontrol edecektir.
Diğerleri zaten bunun çok ağır olabileceğini söylediler, bu yüzden elbette birinden diğerine geçtiğiniz bir nokta var. Şahsen, ikinci kez bir anahtar yazdığımda, refactor olduğum noktadır.
if-else
Anahtarın doğası gereği kötü olduğu fikrini çürütüyorum.
Neden istiyorsun? İyi bir derleyicinin elinde, bir switch deyimi, / else bloklarından (okunması daha kolay olmasının yanı sıra) çok daha verimli olabilir ve herhangi bir türle değiştirildikleri takdirde yalnızca en büyük anahtarların hızlandırılması muhtemeldir. dolaylı arama veri yapısının
'geçiş' sadece bir dil yapısıdır ve tüm dil yapıları bir işi yapmak için araçlar olarak düşünülebilir. Gerçek araçlarda olduğu gibi, bazı araçlar bir göreve diğerinden daha uygundur (resim kancası koymak için bir kızak çekiç kullanmazsınız). Önemli olan 'işi bitirmenin' nasıl tanımlandığıdır. Sürdürülebilir olması gerekiyor mu, hızlı olması gerekiyor mu, ölçeklendirilmesi mi gerekiyor, genişletilebilir olması gerekiyor mu vb.
Programlama sürecinin her noktasında genellikle kullanılabilecek bir dizi yapı ve desen vardır: bir anahtar, bir if-if-series dizisi, sanal fonksiyonlar, atlama tabloları, fonksiyon işaretçileri olan haritalar vb. Tecrübe ile bir programcı belirli bir durum için doğru aracı içgüdüsel olarak bilir.
Herhangi bir yapının güvenle kullanılabilmesi için, kodu koruyan veya gözden geçiren herkesin en az orijinal yazar kadar yetenekli olduğu varsayılmalıdır.
Anahtar, çeşitli nesne türlerini ayırt etmek için oradaysa, muhtemelen bu nesneleri veya bazı sanal yöntemleri tam olarak tanımlamak için bazı sınıfları kaçırıyorsunuzdur ...
C ++ için
Yani bir AbstractFactory atıfta bulunuyorsanız, bir registerCreatorFunc (..) yönteminin genellikle gerekli olan her "yeni" deyim için bir vaka eklemeyi gerektirmekten daha iyi olduğunu düşünüyorum . Daha sonra tüm sınıfların bir makro ile kolayca uygulanabilecek bir creatorFunction (..) oluşturmasına ve kaydetmesine izin vermek (bahsetmeye cesaret edersem). Bunun birçok çerçevenin ortak bir yaklaşım olduğuna inanıyorum. İlk olarak ET ++ 'da gördüm ve bir DECL ve IMPL makrosu gerektiren birçok çerçevenin bunu kullandığını düşünüyorum.
C gibi prosedürel bir dilde, geçiş, alternatiflerden herhangi birinden daha iyi olacaktır.
Nesneye yönelik bir dilde, nesne yapısını, özellikle de polimorfizmi daha iyi kullanan neredeyse her zaman başka alternatifler vardır.
Anahtar ifadeleriyle ilgili sorun, uygulamanın birden çok yerinde çok benzer anahtar blokları oluştuğunda ve yeni bir değer desteğinin eklenmesi gerektiğinde ortaya çıkar. Bir geliştiricinin, uygulamanın etrafına dağılmış anahtar bloklarından birine yeni değer için destek eklemeyi unutması oldukça yaygındır.
Çok biçimlilik ile yeni değer yeni değerin yerini alır ve yeni davranış, yeni sınıfı eklemenin bir parçası olarak eklenir. Bu anahtar noktalarındaki davranış, ya üst sınıftan devralınır, yeni davranış sağlamak için geçersiz kılınır veya süper yöntem soyut olduğunda derleyici hatasını önlemek için uygulanır.
Belirgin bir polimorfizmin olmadığı yerlerde, Strateji modelini uygulamaya değer olabilir .
Ama alternatifiniz büyük bir EĞER ... SONRA ... ELSE bloğuysa, unutun.
İşlev işaretçileri, büyük bir tıknaz anahtar ifadesinin yerini almanın bir yoludur, özellikle işlevleri adlarıyla yakalayabileceğiniz ve onlarla bir şeyler yapabileceğiniz dillerde iyidir.
Tabii ki, switch deyimlerini kodunuzdan zorlamamalısınız ve her zaman yanlış yapma şansınız vardır, bu da aptal gereksiz kod parçalarıyla sonuçlanır. (Bu bazen kaçınılmazdır, ancak iyi bir dil, temiz kalırken artıklığı kaldırmanıza izin vermelidir.)
Bu harika bir bölünme ve fethetme örneği:
Diyelim ki bir çeşit tercümanınız var.
switch(*IP) {
case OPCODE_ADD:
...
break;
case OPCODE_NOT_ZERO:
...
break;
case OPCODE_JUMP:
...
break;
default:
fixme(*IP);
}
Bunun yerine şunu kullanabilirsiniz:
opcode_table[*IP](*IP, vm);
... // in somewhere else:
void opcode_add(byte_opcode op, Vm* vm) { ... };
void opcode_not_zero(byte_opcode op, Vm* vm) { ... };
void opcode_jump(byte_opcode op, Vm* vm) { ... };
void opcode_default(byte_opcode op, Vm* vm) { /* fixme */ };
OpcodeFuncPtr opcode_table[256] = {
...
opcode_add,
opcode_not_zero,
opcode_jump,
opcode_default,
opcode_default,
... // etc.
};
C de opcode_table fazlalığını kaldırmak için nasıl bilmiyorum unutmayın. Belki de bu konuda bir soru yapmak gerekir. :)
En belirgin, dilden bağımsız cevap, bir dizi 'if' kullanmaktır.
Kullandığınız dilde işlev işaretçileri (C) varsa veya 1. sınıf değerleri (Lua) olan işlevlere sahipse, işlevlerden oluşan bir dizi (veya liste) kullanarak "geçiş" işlevine benzer sonuçlar elde edebilirsiniz.
Daha iyi cevaplar istiyorsanız dile daha açık olmalısınız.
Anahtar ifadeleri genellikle iyi bir OO tasarımı ile değiştirilebilir.
Örneğin, bir Hesap sınıfınız var ve hesap türüne göre farklı bir hesaplama yapmak için bir switch deyimi kullanıyorsunuz.
Bunun, farklı hesap türlerini temsil eden ve bir Hesap arabirimi uygulayan bir dizi hesap sınıfı ile değiştirilmesi gerektiğini öneririm.
Daha sonra anahtar gereksiz olur, çünkü tüm hesap türlerine aynı şekilde davranabilirsiniz ve polimorfizm sayesinde hesap türü için uygun hesaplama yapılır.
Neden değiştirmek istediğinize bağlı!
Birçok tercüman opcode yürütme için switch deyimleri yerine 'hesaplanmış gotos' kullanır.
C / C ++ anahtarı hakkında özlediğim Pascal 'in' ve aralıkları. Keşke dizeleri de açabilseydim. Ancak bunlar, bir derleyicinin yemek için önemsiz olmakla birlikte, yapılar ve yineleyiciler ve şeyler kullanılarak yapıldığında zor işlerdir. Aksine, sadece C'nin anahtarı () daha esnek olsaydı, bir anahtarla değiştirebilmeyi dilediğim birçok şey var!
Açma Prensibi'ni kırdığı için geçiş yapmak iyi bir yol değildir. Ben böyle yaparım.
public class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public virtual void Speak()
{
Console.WriteLine("Hao Hao");
}
}
public class Cat : Animal
{
public virtual void Speak()
{
Console.WriteLine("Meauuuu");
}
}
Ve nasıl kullanılacağı (kodunuzu alarak):
foreach (var animal in zoo)
{
echo animal.speak();
}
Temel olarak yaptığımız şey, ebeveynin çocuklarla ne yapacağına karar vermesi yerine sorumluluğu çocuk sınıfına devretmektir.
Ayrıca "Liskov İkame İlkesi" ni okumak isteyebilirsiniz.
İlişkilendirilebilir dizi kullanan JavaScript'te:
bu:
function getItemPricing(customer, item) {
switch (customer.type) {
// VIPs are awesome. Give them 50% off.
case 'VIP':
return item.price * item.quantity * 0.50;
// Preferred customers are no VIPs, but they still get 25% off.
case 'Preferred':
return item.price * item.quantity * 0.75;
// No discount for other customers.
case 'Regular':
case
default:
return item.price * item.quantity;
}
}
bu olur:
function getItemPricing(customer, item) {
var pricing = {
'VIP': function(item) {
return item.price * item.quantity * 0.50;
},
'Preferred': function(item) {
if (item.price <= 100.0)
return item.price * item.quantity * 0.75;
// Else
return item.price * item.quantity;
},
'Regular': function(item) {
return item.price * item.quantity;
}
};
if (pricing[customer.type])
return pricing[customer.type](item);
else
return pricing.Regular(item);
}
İf / else için başka bir oy. Ben büyük bir dava veya anahtar ifadeleri hayranı değilim çünkü onları kullanmayan bazı insanlar var. Büyük / küçük harf veya anahtar kullanırsanız kod daha az okunabilir. Belki sizin için daha az okunabilir değil, ama komutu kullanmak için hiç ihtiyaç duymamış olanlar için.
Aynı şey nesne fabrikaları için de geçerli.
/ Else blokları herkesin aldığı basit bir yapı ise. Sorunlara neden olmadığınızdan emin olmak için yapabileceğiniz birkaç şey var.
Birincisi - İfadelerin birkaç kezden fazla olup olmadığını girintilemeyin. Kendinizi girintili buluyorsanız, yanlış yaparsınız.
if a = 1 then
do something else
if a = 2 then
do something else
else
if a = 3 then
do the last thing
endif
endif
endif
Gerçekten kötü - bunun yerine bunu yapın.
if a = 1 then
do something
endif
if a = 2 then
do something else
endif
if a = 3 then
do something more
endif
Optimizasyon lanet olsun. Kodunuzun hızı konusunda o kadar büyük bir fark yaratmaz.
İkincisi, sürece belirli kod bloğu aracılığıyla dağınık açık hale getirmek için yeterli sonları ifadeleri olduğu sürece bir if bloğunu kırmak için sabırsızlanıyorum
procedure processA(a:int)
if a = 1 then
do something
procedure_return
endif
if a = 2 then
do something else
procedure_return
endif
if a = 3 then
do something more
procedure_return
endif
end_procedure
EDIT : On Switch ve neden grok zor olduğunu düşünüyorum:
İşte bir anahtar ifadesi örneği ...
private void doLog(LogLevel logLevel, String msg) {
String prefix;
switch (logLevel) {
case INFO:
prefix = "INFO";
break;
case WARN:
prefix = "WARN";
break;
case ERROR:
prefix = "ERROR";
break;
default:
throw new RuntimeException("Oops, forgot to add stuff on new enum constant");
}
System.out.println(String.format("%s: %s", prefix, msg));
}
Benim için buradaki sorun, C benzeri dillerde uygulanan normal kontrol yapılarının tamamen kırılmış olmasıdır. Bir kontrol yapısının içine birden fazla kod satırı yerleştirmek istiyorsanız, kaşlı ayraçlar veya bir başlangıç / bitiş ifadesi kullanmanızın genel bir kuralı vardır.
Örneğin
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
Benim için (ve eğer yanlışsam beni düzeltebilirsiniz), Case deyimi bu kuralı pencereden dışarı atar. Koşullu olarak yürütülen bir kod bloğu bir başlangıç / bitiş yapısı içine yerleştirilmez. Bu nedenle, Case'in kavramsal olarak kullanılmayacak kadar farklı olduğuna inanıyorum.
Umarım sorunuzu cevaplamıştır.