2010-06-28 90 views
12

在閱讀了最優秀的書「頭第一設計模式」之後,我開始向同事們宣傳模式和設計原則的好處。在頌揚我最喜歡的模式 - 策略模式的優點時,我被問到一個讓我停下來的問題。當然,策略使用繼承和組合,而當我的一個同事問「爲什麼要使用抽象基類而不是具體類時」時,我正在談論關於「編程接口(或超類型)而不是實現」的問題。 。
我只能想出「你強迫你的子類實現抽象方法並阻止它們實例化ABC」。但說實話,這個問題引起了我的興趣。這些是使用抽象基類超過層次結構頂部的具體類的唯一好處嗎?作爲超類型的抽象基類與具體類

+0

在我看來是的。但是我認爲這是強迫某個從這個班級繼承的人實施一種方法的語言的一個非常重要的「特徵」。有時你需要創建一個普通的類,但不能實現所有的功能,因爲它太具體了。 – KroaX 2010-06-28 17:52:07

回答

22

如果您需要實施特定方法,請使用接口。如果共享邏輯可以被抽出,則使用抽象基類。如果功能的基本功能是完全獨立的,那麼您可以使用concreate類作爲基礎。抽象基類和接口不能直接實例化,這是其中一個優點。如果你可以使用具體類型,那麼你需要做重寫方法,並且它有一個「代碼味道」。

+0

+1。很好地運行什麼以及什麼時候使用。 – NotMe 2010-06-28 17:55:37

+1

咦?爲什麼方法壓倒一切的氣味? – ladenedge 2010-06-28 18:02:10

+5

虛擬方法的代碼在未破壞某些隱含的行爲契約的情況下在未來更難改變。所以在.NET中,他們決定製定一個可重寫的方法必須是一個有意識的決定,應該仔細考慮。因此,對於實現者沒有指示的任何可覆蓋的方法都是「代碼味道」。 – 2010-06-28 18:06:21

1

是的,雖然你也可以使用一個接口強制類實現特定的方法。

使用抽象類而不是具體類的另一個原因是抽象類顯然不能被實例化。有時你也不希望發生這種情況,所以抽象類是要走的路。

5

程序接口,而不是執行與抽象和具體類沒有多大關係。記住template method pattern?類,抽象或具體,是實現細節。

而使用抽象類而不是具體類的原因是你可以在不實現它們的情況下調用方法,而是通過將它們實現爲子類來代替。

面向接口編程是一個不同的東西 - 它是定義您的API做什麼,而不是如何做它。這由接口表示。

注意一個關鍵區別 - 您可以有protected abstract方法,這意味着這是實現細節。但是所有的接口方法都是公共的 - 是API的一部分。

+0

爲了補充一點,「程序到實施」將採取諸如使用反思來訪問班級的私人字段等內容。 – 2010-06-28 18:00:45

1

首先,戰略模式應該幾乎從不在現代C#中使用。它主要針對Java等不支持函數指針,委託或頭等功能的語言。您將在IComparer等接口中的舊版C#中看到它。

至於抽象基類與具體類,Java中的答案總是「在這種情況下什麼效果更好?」如果你的策略可以共享代碼,那麼盡一切辦法讓他們這樣做。

設計模式並不是關於如何做某事的指示。他們是對我們已經完成的事情進行分類的方式。

0

如果客戶依賴「隱含行爲契約」,​​它會針對實現進行編程並針對無擔保行爲進行編程。在遵循合同時覆蓋方法只會暴露客戶端中的錯誤,而不會導致錯誤。

OTOH,如果所討論的方法是非虛擬的,那麼假定不存在契約的錯誤不太可能導致問題,即覆蓋它不會因爲它不能被覆蓋而導致問題。只有當原始方法的實施發生變化(仍遵守合同時)才能破壞客戶端。

0

基類是否應該是抽象的或具體的問題取決於恕我直言,主要是基類對象是否只實現了類中所有對象共有的行爲將是有用的。考慮一下WaitHandle。在它上面調用「等待」會導致代碼阻塞,直到某些條件滿足爲止,但沒有常用的方法告訴WaitHandle對象它的條件滿足。如果可以實例化一個「WaitHandle」,而不是僅僅能夠實例化派生類型的實例,那麼這樣的對象將不得不永遠等待,或者永遠等待。後者的行爲將是無用的;前者可能是有用的,但可以通過靜態分配的ManualResetEvent來實現(我認爲後者浪費了一些資源,但是如果它是靜態分配的,則總資源損失應該是微不足道的)。

在很多情況下,我認爲我的選擇是使用對接口的引用而不是抽象的基類,而是爲接口提供一個提供「模型實現」的基類。所以任何一個會使用對MyThing的引用的地方,都會提供對「iMyThing」的引用。很可能99%(甚至100%)的iMyThing對象實際上是一個MyThing,但如果有人需要擁有繼承其他東西的iMyThing對象,那麼可以這樣做。

1

抽象基類通常用於設計人員想要強制執行架構模式的場景,其中某些任務將由所有類以相同方式執行,而其他行爲依賴於子類。 例如:

public abstract class Animal{ 

public void digest(){ 

} 

public abstract void sound(){ 

} 
} 

public class Dog extends Animal{ 
public void sound(){ 
    System.out.println("bark"); 
} 
} 

Stratergy模式要求設計人員使用那裏有alogirthms的行爲的情況下,家庭成分的行爲。

0

不想抽象基類在以下情形:

  1. 甲基類不能與出的子類存在=>基類是簡單地抽象,它可以」 T被實例化。
  2. 基類不能有一個方法的完整或具體的實現=>一個方法的實現是基類是不完整的,只有子類可以提供完整的實現。
  3. 基類提供的方法實現的模板,但它仍然依賴於混凝土類來完成該方法實施 - Template_method_pattern

一個簡單的例子來說明上述各點

Shape是抽象的,它不能沒有像Rectangle這樣的混凝土形狀。由於不同的形狀具有不同的公式,因此不能在Shape類中執行Shape。最好的辦法來處理方案:離開draw()實施的子類

abstract class Shape{ 
    int x; 
    int y; 
    public Shape(int x,int y){ 
     this.x = x; 
     this.y = y; 
    } 
    public abstract void draw(); 
} 
class Rectangle extends Shape{ 
    public Rectangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Rectangle using x and y : length * width 
     System.out.println("draw Rectangle with area:"+ (x * y)); 
    } 
} 
class Triangle extends Shape{ 
    public Triangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Triangle using x and y : base * height /2 
     System.out.println("draw Triangle with area:"+ (x * y)/2); 
    } 
} 
class Circle extends Shape{ 
    public Circle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Circle using x as radius (PI * radius * radius 
     System.out.println("draw Circle with area:"+ (3.14 * x * x)); 
    } 
} 

public class AbstractBaseClass{ 
    public static void main(String args[]){ 
     Shape s = new Rectangle(5,10); 
     s.draw(); 
     s = new Circle(5,10); 
     s.draw(); 
     s = new Triangle(5,10); 
     s.draw(); 
    } 
} 

輸出:

draw Rectangle with area:50 
draw Circle with area:78.5 
draw Triangle with area:25 

上面的代碼覆蓋點1和點2,您可以更改draw()方法爲模板方法,如果基類有一些實現並調用子類方法來完成draw()函數。

現在,同樣的例子與模板方法模式:

abstract class Shape{ 
    int x; 
    int y; 
    public Shape(int x,int y){ 
     this.x = x; 
     this.y = y; 
    } 
    public abstract void draw(); 

    // drawShape is template method 
    public void drawShape(){ 
     System.out.println("Drawing shape from Base class begins"); 
     draw(); 
     System.out.println("Drawing shape from Base class ends");  
    } 
} 
class Rectangle extends Shape{ 
    public Rectangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Rectangle using x and y : length * width 
     System.out.println("draw Rectangle with area:"+ (x * y)); 
    } 
} 
class Triangle extends Shape{ 
    public Triangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Triangle using x and y : base * height /2 
     System.out.println("draw Triangle with area:"+ (x * y)/2); 
    } 
} 
class Circle extends Shape{ 
    public Circle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Circle using x as radius (PI * radius * radius 
     System.out.println("draw Circle with area:"+ (3.14 * x * x)); 
    } 
} 

public class AbstractBaseClass{ 
    public static void main(String args[]){ 
     Shape s = new Rectangle(5,10); 
     s.drawShape(); 
     s = new Circle(5,10); 
     s.drawShape(); 
     s = new Triangle(5,10); 
     s.drawShape(); 
    } 
} 

輸出:

Drawing shape from Base class begins 
draw Rectangle with area:50 
Drawing shape from Base class ends 
Drawing shape from Base class begins 
draw Circle with area:78.5 
Drawing shape from Base class ends 
Drawing shape from Base class begins 
draw Triangle with area:25 
Drawing shape from Base class ends 

一旦你決定,你必須作出方法abstract,你有兩個選擇:要麼用戶interfaceabstract類。您可以在interface中聲明您的方法,並將abstract類定義爲實現interface的類。