Davranışın parametresinin türünü açtığı bir yöntem gördüğümde, bu yöntem aslında method parametresine aitse hemen ilk önce düşünürüm. Örneğin, aşağıdaki gibi bir yöntem yerine:
public void sort(List values) {
if (values instanceof LinkedList) {
// do efficient linked list sort
} else { // ArrayList
// do efficient array list sort
}
}
Bunu yapardım:
values.sort();
// ...
class ArrayList {
public void sort() {
// do efficient array list sort
}
}
class LinkedList {
public void sort() {
// do efficient linked list sort
}
}
Davranışı, ne zaman kullanılacağını bilen yere taşıyoruz. Uygulamanın türlerini veya ayrıntılarını bilmenize gerek olmayan gerçek bir soyutlama yaratırız . Durumunuz için, bu yöntemi yazıp geçersiz kılmak için orijinal sınıftan (arayacağım O
) taşımak daha mantıklı olabilir . Yöntem bazı nesnelerde çağrılırsa , içindeki farklı davranışa geçin ve geçersiz kılın . Başlangıçta denilen yerden veri bitleri varsa veya yöntem yeterli yerlerde kullanılıyorsa, orijinal yöntemi bırakıp temsilci atayabilirsiniz:A
B
doIt
doIt
A
B
doIt
class O {
int x;
int y;
public void doIt(A a) {
a.doIt(this.x, this.y);
}
}
Yine de biraz daha derine dalabiliriz. Bunun yerine bir boole parametresi kullanma önerisine bakalım ve iş arkadaşınızın nasıl düşündüğü hakkında neler öğrenebileceğimizi görelim. Onun önerisi:
public void doIt(A a, boolean isTypeB) {
if (isTypeB) {
// do B stuff
} else {
// do A stuff
}
}
Bu instanceof
, ilk örneğimde kullandığım gibi korkunç bir şey gibi görünüyor , ancak bu kontrolü dışsallaştırıyoruz. Bu, onu iki yoldan biriyle çağırmamız gerektiği anlamına gelir:
o.doIt(a, a instanceof B);
veya:
o.doIt(a, true); //or false
İlk olarak, çağrı noktasının ne tür olduğuna dair hiçbir fikri yoktur A
. Bu nedenle, booleanları tamamen aşağı mı geçirmeliyiz? Bu gerçekten kod tabanının her yerinde istediğimiz bir kalıp mı? Hesaplamamız gereken üçüncü bir tür varsa ne olur? Eğer yöntemin adı bu ise, onu türüne taşımalı ve sistemin polimorfik olarak bizim için uygulamayı seçmesine izin vermeliyiz.
İkinci şekilde, çağrı noktasındaki türünü zaten bilmeliyiza
. Genellikle bu, oradaki örneği oluşturduğumuz veya bu türden bir örneği parametre olarak aldığımız anlamına gelir. Üzerinde bir yöntemi oluşturma O
bir sürer B
çalışacak burada. Derleyici hangi yöntemi seçeceğini bilecektir. Bu gibi değişikliklerden geçerken , en azından gerçekten nereye gittiğimizi anlayana kadar , çoğaltma yanlış soyutlamayı yaratmaktan daha iyidir . Tabii ki, bu noktaya ne değiştirirsek değiştirelim, gerçekten yapılmadığını öneriyorum.
A
Ve arasındaki ilişkiye daha yakından bakmamız gerekiyor B
. Genellikle, bize miras yerine kompozisyonu tercih etmemiz gerektiği söylenir . Bu, her durumda doğru değildir, ama biz kazmak kez vakaların şaşırtıcı sayıda doğrudur. B
Devralır A
, biz inanıyoruz anlamına B
bir olduğunu A
. B
tıpkı A
biraz farklı çalışması dışında kullanılmalıdır . Fakat bu farklar nelerdir? Farklılıklara daha somut bir isim verebilir miyiz? Değil B
bir A
, ama gerçekten olabilir A
bir X
var A'
ya da B'
? Bunu yapsaydık kodumuz nasıl olurdu?
Yöntemi A
daha önce önerildiği şekilde üzerine taşırsak, X
içine bir örnek A
ekleyebilir ve bu yönteme şu şekilde temsilci atayabiliriz X
:
class A {
X x;
A(X x) {
this.x = x;
}
public void doIt(int x, int y) {
x.doIt(x, y);
}
}
Uygulayabiliriz A'
ve B'
kurtulabiliriz B
. Daha örtük olabilecek bir kavram için bir ad vererek kodu geliştirdik ve derleme zamanı yerine çalışma zamanında bu davranışı ayarlamamıza izin verdik. A
aslında daha az soyutlaştı. Genişletilmiş miras ilişkisi yerine, temsilci bir nesne üzerinde yöntemler çağırıyor. Bu nesne soyuttur, ancak yalnızca uygulamadaki farklılıklara odaklanır.
Yine de bakılması gereken son bir şey var. İş arkadaşınızın teklifine geri dönelim. Tüm çağrı sitelerinde sahip olduğumuz türü açıkça biliyorsak, aşağıdaki A
gibi çağrılar yapmalıyız:
B b = new B();
o.doIt(b, true);
Daha önce bu ya da A
olan bir kompozisyon oluştururken varsaymıştık . Ama belki bu varsayım bile doğru değildir. Bu tek yer arasındaki bu fark mı ve konular? Eğer öyleyse, belki biraz farklı bir yaklaşım alabiliriz. Hala ya da olan bir tane var , ama ait değil . Sadece umurunda, bu yüzden sadece şuraya geçelim :X
A'
B'
A
B
X
A'
B'
A
O.doIt
O.doIt
class O {
int x;
int y;
public void doIt(A a, X x) {
x.doIt(a, x, y);
}
}
Şimdi çağrı sitemiz şöyle görünüyor:
A a = new A();
o.doIt(a, new B'());
Bir kez daha B
kaybolur ve soyutlama daha odaklı hale gelir X
. Ancak bu sefer A
daha az şey bilmek daha da kolay. Daha az soyut.
Kod tabanındaki yinelemeyi azaltmak önemlidir, ancak yinelemenin neden ilk etapta gerçekleştiğini düşünmeliyiz. Çoğaltma, dışarı çıkmaya çalışan daha derin soyutlamaların bir işareti olabilir.