設計模式之美學習-設計原則-面向對象基本概念(一)

主流的幾種編程模式

面向對象編程、面向過程編程、函數式編程 java

面向過程編程的語言:編程

Basic、Pascal、C編程語言

什麼是面向對象編程

面向對象編程是一種編程範式或編程風格。它以類或對象做爲組織代碼的基本單元,並將封裝、抽象、繼承、多態四個特性,做爲代碼設計和實現的基石 。ide

面向對象編程語言是支持類或對象的語法機制,並有現成的語法機制,能方便地實現面向對象編程四大特性(封裝、抽象、繼承、多態)的編程語言。函數式編程

什麼是面向過程

面向過程編程也是一種編程範式或編程風格。它以過程(能夠理解爲方法、函數、操做)做爲組織代碼的基本單元,以數據(能夠理解爲成員變量、屬性)與方法相分離爲最主要的特色。函數

面向過程風格是一種流程化的編程風格,經過拼接一組順序執行的方法來操做數據完成一項功能。阿里雲

面向對象編程和麪向過程具備哪些優點

對於大規模複雜程序的開發,程序的處理流程並不是單一的一條主線,而是錯綜複雜的網狀結構。面向對象編程比起面向過程編程,更能應對這種複雜類型的程序開發。url

面向對象編程相比面向過程編程,具備更加豐富的特性(封裝、抽象、繼承、多態)。利用這些特性編寫出來的代碼,更加易擴展、易複用、易維護。spa

從編程語言跟機器打交道的方式的演進規律中,咱們能夠總結出:面向對象編程語言比起面向過程編程語言,更加人性化、更加高級、更加智能。設計

 

面向過程適用於較小規模程序開發,快速開發 不須要考慮結構設計   結構簡單

接口和抽象類的區別

接口

1.接口經過interface修飾

2.接口默認就是abstract修飾 加不加都同樣

3.接口不能擁有成員變量

4.接口中的定義的變量編譯以後都是static final

5.接口中的方法都是抽象的 

6.java8 能夠經過default關鍵字 設置默認實現

7.接口能夠多實現

抽象類

1.抽象類經過abstract修飾

2.抽象類不能實例化

3.抽象類中能夠含有抽象方法不對方法進行實現

4.抽象類只能單繼承 同時子類必須對抽象方法進行實現 除非子類也是抽象方法

何時選擇抽象類何時選擇接口

抽象類

  1. 對代碼的複用 同時表示的is a的關係的時候

接口

  1. 表示has-a(具備某種特性)  同時是爲了隔離實現 解決抽象 提升代碼的擴展性

如何基於原則"面向接口而非實現編程"

應用這條原則,能夠將接口和實現相分離,封裝不穩定的實現,暴露穩定的接口。上游系統面向接口而非實現編程,不依賴不穩定的實現細節,這樣當實現發生變化的時候,上游系統的代碼基本上不須要作改動,以此來下降耦合性,提升擴展性。

例子

若是咱們系統中須要有個須要上傳下載圖片到阿里雲的功能步驟以下:

1.建立 bucket(你能夠簡單理解爲存儲目錄)

2.生成 access token 訪問憑證、攜帶 access token

3.上傳圖片到指定的 bucket 中。

public class AliyunImageStore {
  //...省略屬性、構造函數等...
  
  public void createBucketIfNotExisting(String bucketName) {
    // ...建立bucket代碼邏輯...
    // ...失敗會拋出異常..
  }
  
  public String generateAccessToken() {
    // ...根據accesskey/secrectkey等生成access token
  }
  
  public String uploadToAliyun(Image image, String bucketName, String accessToken) {
    //...上傳圖片到阿里雲...
    //...返回圖片存儲在阿里雲上的地址(url)...
  }
  
  public Image downloadFromAliyun(String url, String accessToken) {
    //...從阿里雲下載圖片...
  }
}

// AliyunImageStore類的使用舉例
public class ImageProcessingJob {
  private static final String BUCKET_NAME = "ai_images_bucket";
  //...省略其餘無關代碼...
  
  public void process() {
    Image image = ...; //處理圖片,並封裝爲Image對象
    AliyunImageStore imageStore = new AliyunImageStore(/*省略參數*/);
    imageStore.createBucketIfNotExisting(BUCKET_NAME);
    String accessToken = imageStore.generateAccessToken();
    imagestore.uploadToAliyun(image, BUCKET_NAME, accessToken);
  }
  
}

面向接口版本

這個需求須要更改,從阿里雲改成私有云 私有云並不須要accessToken

可能會有下面2種改動

方式一

1.在原有類的名字修改改成PrivateImageStore 

2.刪除無用的方法將uploadToAliyun改成upload並移除accessToken參數

方式二

從新定義一個新類 而後上層代碼移除 從新完整替換

面向接口編程而非實現編程原則

1.函數的命名不能暴露任何實現細節。好比,前面提到的 uploadToAliyun() 就不符合要求,應該改成去掉 aliyun 這樣的字眼,改成更加抽象的命名方式,好比:upload()。

2.封裝具體的實現細節。好比,跟阿里雲相關的特殊上傳(或下載)流程不該該暴露給調用者。咱們對上傳(或下載)流程進行封裝,對外提供一個包裹全部上傳(或下載)細節的方法,給調用者使用。

3.爲實現類定義抽象的接口。具體的實現類都依賴統一的接口定義,聽從一致的上傳功能協議。使用者依賴接口,而不是具體的實現類來編程

代碼

public interface ImageStore {
  String upload(Image image, String bucketName);
  Image download(String url);
}

public class AliyunImageStore implements ImageStore {
  //...省略屬性、構造函數等...

  public String upload(Image image, String bucketName) {
    createBucketIfNotExisting(bucketName);
    String accessToken = generateAccessToken();
    //...上傳圖片到阿里雲...
    //...返回圖片在阿里雲上的地址(url)...
  }

  public Image download(String url) {
    String accessToken = generateAccessToken();
    //...從阿里雲下載圖片...
  }

  private void createBucketIfNotExisting(String bucketName) {
    // ...建立bucket...
    // ...失敗會拋出異常..
  }

  private String generateAccessToken() {
    // ...根據accesskey/secrectkey等生成access token
  }
}

// 上傳下載流程改變:私有云不須要支持access token
public class PrivateImageStore implements ImageStore  {
  public String upload(Image image, String bucketName) {
    createBucketIfNotExisting(bucketName);
    //...上傳圖片到私有云...
    //...返回圖片的url...
  }

  public Image download(String url) {
    //...從私有云下載圖片...
  }

  private void createBucketIfNotExisting(String bucketName) {
    // ...建立bucket...
    // ...失敗會拋出異常..
  }
}

// ImageStore的使用舉例
public class ImageProcessingJob {
  private static final String BUCKET_NAME = "ai_images_bucket";
  //...省略其餘無關代碼...
  
  public void process() {
    Image image = ...;//處理圖片,並封裝爲Image對象
    ImageStore imageStore = new PrivateImageStore(...);
    imagestore.upload(image, BUCKET_NAME);
  }
}

 不要濫用此原則,須要評估改動的可能性  否則接口滿天飛 致使沒必要要的開發負擔

爲什麼說多用組合少用繼承

錯誤例子

若是咱們設計一個關於鳥的類,大部分鳥都會飛,咱們設計一個抽象的鳥類AbstractBird 有一個fly()方法  讓具體的鳥類繼承這個抽象類好比麻雀、鴿子、烏鴉等 

需求須要增長一個鴕鳥的類 可是鴕鳥不會飛 怎麼辦,能夠有2個選擇

方案1:

public class AbstractBird {
  //...省略其餘屬性和方法...
  public void fly() { //... }
}

public class Ostrich extends AbstractBird { //鴕鳥
  //...省略其餘屬性和方法...
  public void fly() {
    throw new UnSupportedMethodException("I can't fly.'");
  }
}

讓不會飛的鳥重寫這個飛的方法 並拋出一個異常,可是這種方案有個問題就是違反了最少知識原則或者迪米特法 對外暴露了沒必要要的接口 增長了調用方的錯誤率

方案2:

鳥類下面再定義2個類 會飛的鳥和不會飛的鳥  會飛的鳥繼承會飛的鳥類 鴕鳥繼承不會飛的鳥類  

 

 這樣繼承的層級就變深了 提升了代碼的複雜性,若是咱們還要區分鳥會不會叫呢  這樣咱們又得在會飛的鳥和不會飛的鳥下面各定義2個類 分別叫會飛的鳥會叫 會飛的鳥不會叫    不會飛的鳥會叫 不會飛的叫不會叫

 

 看似解決了 可是若是還要區分

使用組合例子

//會飛的
public interface Flyable {
  void fly();
}
//會叫的
public interface Tweetable {
  void tweet();
}
//會下單的
public interface EggLayable {
  void layEgg();
}
public class Ostrich implements Tweetable, EggLayable {//鴕鳥
  //... 省略其餘屬性和方法...
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}
public class Sparrow impelents Flayable, Tweetable, EggLayable {//麻雀
  //... 省略其餘屬性和方法...
  @Override
  public void fly() { //... }
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}

這有個缺點就是 全部鳥類都要去實現一遍 並不能達到複用 使用組合加委託 分別再定義3個行爲的實現類

public interface Flyable {
  void fly();
}
public class FlyAbility implements Flyable {
  @Override
  public void fly() { //... }
}
//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鴕鳥
  private TweetAbility tweetAbility = new TweetAbility(); //組合
  private EggLayAbility eggLayAbility = new EggLayAbility(); //組合
  //... 省略其餘屬性和方法...
  @Override
  public void tweet() {
    tweetAbility.tweet(); // 委託
  }
  @Override
  public void layEgg() {
    eggLayAbility.layEgg(); // 委託
  }
}

組合並非完美的 繼承也不是一無可取,僅僅是爲了代碼複用 並無is-a關係的 屬於濫用

在具備is-a關係 而且層級不深 結構不復雜能夠大膽的使用繼承

相關文章
相關標籤/搜索