Java-Design-Pattern

From YuntechWiki

Jump to: navigation, search

Java與設計樣式

Contents

UML類別圖介紹

  • UML(統一塑模語言)是一種用來建立程式模型的圖形語言,可以說明程式碼中物件之間的關係。
  • UML中有好幾種不同的圖,分別是使用者案例圖、循序圖、合作圖、類別圖、物件圖、元件圖、佈署圖、狀態圖、活動圖。
  • 類別圖:靜態的結構,表示一個系統類別的存在和邏輯觀點上的關係,而不是在於描述它的行為。主要目的為表示出系統中概括性的名字與模式、敘述類別間的合作關係。
  • UML類別圖不僅描述了類別,而且說明了類別間的關係:
  • 類別的描述
  • Class diagram.png
  • 每個屬性和方法的前面都有一個存取權限(能見度)的符號。
  • +:public
  • -:private
  • #:protected
  • 類別間關係的說明(繼承、包含、使用)
  • 繼承,is-a關係
  • 繼承.png
  • B繼承了A,也就是B is a A的意思。例如:爬蟲類動物是一種(is a)動物。
  • 包含,has-a關係
  • 聚合(aggregation):
  • 聚合.png
  • A聚合了B,也就是A擁有B,B是A的一部分,但是這種擁有關係比較〝弱〞,比如說桃園國際機場擁有一台X飛機,但是這台飛機飛往高雄後,就變成高雄小港機場擁有X飛機,而不是桃園國際機場擁有了。
  • 組合(composition):
  • 組合.png
  • A是由B組成的,也就是A擁有B,B是A的一部分,但是這種擁有關係比較〝強〞,比如說X飛機擁有輪子的關係,沒有輪子飛機就不能正常運作。
  • 使用,use-a關係
  • 依賴:
  • 依賴.png
  • A依賴B,也就是A一定要使用B,比如說人跟氧氣的關係,人必須依賴氧氣才能生存。
  • 關聯:
  • 關聯.png
  • A關聯B,也就是A使用B,但是它們之間的關係並沒有像依賴這麼強烈,比如說人跟氣候的關係,人跟氣候有關聯,但是並不是只能生存在一種氣候之中。

Design Patterns 介紹

  • 最早設計模式(Design Patterns)是從建築學所產生的名詞,到了90年代開始,設計模式慢慢在軟體設計中出現,以Gamma、Helm、Johnson、Vlissides合著的著作《DESIGN PATTERNS:Elements of Reusable Object-Oriented Software》最為大家所知,大家並為四位作者取了一個通行的暱稱─GoF。
  • 此著作並非GoF憑空所創造的,而是歸納一些用於解決在特定環境、重複出現、特定問題的解決方案,且這些方案是經過驗證,得到大家認可的,所以把它稱作設計模式。

物件導向設計策略

  • 依人機介面程式設計(Designing to interface)。
  • 盡量用聚合代替繼承。
  • 聚合(aggregation):表示一種弱的『擁有』關係,A物件可以包含B物件,但B物件並不是A物件的一部分,例如:一個球隊(team)包含了球員(player),但如果有一天球隊解散了,球員還是可以去其他隊伍。
Aggregation.png
  • 找出變化並封裝之。
  • 封裝(Encapsulation):將程式碼切割成許多類別(class),使各類別之間的關連性降到最低,這麼一來比較不會產生「牽一髮而動全身」的狀況,降低類別間相互依賴的程度,也等於是降低複雜度,讓開發與維護更容易。

設計模式

Facade外觀模式

  • 意圖:為子系統中的一組公開方法定義一個統一的操作介面(Facade),外觀模式定義了一個更高層的操作介面(Facade),使子系統更加容易使用。


Facade2.png
class AModule{
public void testA(){
System.out.println("現在在A模組中操作testA方法");
}
}
class BModule{
public void testB(){
System.out.println("現在在B模組中操作testB方法");
}
}
class CModule{
public void testC(){
System.out.println("現在在C模組中操作testC方法");
}
}
class Facade{
AModule a=new AModule();
BModule b=new BModule();
CModule c=new CModule();
 
public void test(){
a.testA();
b.testB();
c.testC();
}
}
public class Client {
public static void main(String[] args) {
Facade create=new Facade();
create.test();
}
}
  • 外觀模式(Facade)的主要目的並不是給子系統增加新的功能介面,而是為了讓外部減少與子系統內多個模組(模組A、B、C...)的互動,而讓外部更容易使用子系統。
  • 雖然有了外觀(Facade),但是用戶端(Client)如果有其它需求,也是可以繞過外觀(Facade),直接呼叫子系統內的模組介面。

Command指令模式

  • 意圖:將一個請求封裝為一個物件(ConcreteCommand),讓你可用不同的請求(ConcreteCommandA、B...)對客戶進行參數化。


Command.png
  • Command:定義指令的介面,宣告執行的方法。
  • ConcreteCommand:指令介面的實現,可以有多個(ConcreteCommandA、B...),通常持有接收者(Receiver)物件,並呼叫接收者的功能來完成指令要執行的操作。
  • Receiver:接收者,真正執行指令地方。任何類別都可能成為一個接收者,只要它能夠實現指令要求實現的對應功能。
  • Invoker:要求指令執行請求,通常持有指令物件(Command),可以持有很多指令物件,這是用戶端(Client)真正觸發指令並要求指令執行對應操作的地方。
interface Command{
public void execute();
}
class ConcreteCommand implements Command{
private Receiver receiver=null;
 
public ConcreteCommand(Receiver receiver){
this.receiver=receiver;
}
public void execute(){
receiver.action();
}
}
class Receiver{
public void action(){
System.out.println("真正執行指令操作的功能程式");
}
}
class Invoker{
private Command command=null;
public void setCommand(Command command){
this.command=command;
}
public void runCommand(){
command.execute();
}
}
public class Client {
public static void main(String[] args) {
Receiver receiver=new Receiver();
Command command=new ConcreteCommand(receiver);
Invoker invoker=new Invoker();
invoker.setCommand(command);
invoker.runCommand();
}
}
  • 指令模式使發起指令者─用戶端(Client),和真正實作指令者─接收者(Receiver)完全解耦,也就是說用戶端完全不知道實作指令是誰。
  • 由於發起指令者和實際的實現完全解耦,因此擴充新的指令就變得相當容易,只需要實現新的指令物件(ConcreteCommand)即可。

Observer觀察者模式

  • 意圖:定義一種一對多的依賴關係,讓多個觀察者(ConcreteObserver)同時監聽某個主題(ConcreteSubject),當主題狀態發生改變時,會通知所有觀察者,使它們能自動更新。
  • 範例:你可以將此模式想像成日常生活中常用的Facebook一樣,假如你在瀏覽Facebook時,發現一個你很有興趣的粉絲專頁,你便按讚追蹤此專頁,此動作就像你是一個觀察者對一個有興趣的主題做註冊訂閱的感覺,當粉絲專頁一有新的動態消息,便會通知所有的觀察者做更新的動作,此時你在自己的動態消息中就能接收到粉絲專頁的新訊息。
Observer3.png
  • Subject:目標類別,通常具有以下功能:
  • 一個目標可以被多個觀察者觀察。
  • 目標提供對觀察者新增(attach)和刪除(detach)的服務。
  • 當目標的狀態發生變化時,目標負責通知(notifyObservers)所有有效的觀察者。
  • Observer:定義觀察者的介面,提供目標通知時對應的更新方法(update)。
  • ConcreteSubject:實際的目標實現類別,用來維護目標狀態,當有所變化時,通知所有有效觀察者執行對應的處理。
  • ConcreteObserver:觀察者的實作方式類別,用來接收目標的通知,並進行對應的處理。
import java.util.*;
class Subject{ //目標類別,可被多個觀察者觀察
private List<Observer> observers=new ArrayList<Observer>();//ArrayList類別實作了List介面,List介面是Collection介面的子介面,主要增加了根據索引取得物件的方法
//在此表示,此陣列名稱叫observers,只能存放的是觀察者物件
public void attach(Observer observer){ //提供觀察者註冊
observers.add(observer);
}
public void detach(Observer observer){ //提供觀察者退訂
observers.remove(observer);
}
protected void notifyObservers(){ //通知
for(int i=0;i<observers.size();i++){
Observer observer=observers.get(i);
observer.update(this);
}
}
}
class ConcreteSubject extends Subject{ //實際的目標實現類別,用來維護目標狀態,當狀態一有改變,便須通知各個觀察者
private String subjectState;
public String getSubjectState(){
return subjectState;
}
public void setSubjectState(String subjectState){
this.subjectState=subjectState;
this.notifyObservers();
}
}
interface Observer{
public void setName(String name);
public String getName();
public void update(Subject subject);
}
class ConcreteObserver implements Observer{ //觀察者的實作方式類別,用來接收目標,更新自己的狀態以保持和目標狀態一致
private String observerState;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public void update(Subject subject){
observerState=((ConcreteSubject)subject).getSubjectState();
System.out.println(name+" get "+observerState);
}
}
public class Client {
public static void main(String[] args) {
ConcreteSubject s=new ConcreteSubject();
Observer A=new ConcreteObserver();
A.setName("A");
Observer B=new ConcreteObserver();
B.setName("B");
Observer C=new ConcreteObserver();
C.setName("C");
 
s.attach(A);//註冊
s.attach(B);
s.attach(C);
 
s.setSubjectState("message!");//廣播
}
}

State狀態模式

  • 意圖:當一個物件的狀態需要改變時,它所對應的方法、功能(behavior)也會改變,這時這個物件類別(ConcreteStateA)會轉換成另一個類別(ConcreteStateB)。
  • 範例:假設你要設計一台只賣一種飲料的販賣機,首先,你必須思考這台販賣機可能會遇到的情況(狀態),它總共有4種狀態,分別是無錢幣狀態、有錢幣狀態、銷售狀態、已售完狀態。通常一台機器都會有一個初始狀態,以販賣機來講,就是等待購買者的無錢幣狀態;當有人投入錢幣後,就會觸發整個狀態的轉移,從無錢幣狀態轉換成有錢幣狀態;當投入足夠的錢並按下購買按鈕後,狀態將從有錢幣狀態轉換成銷售狀態,準備取得飲料;取得飲料後,機器將會判斷飲料是否還有存貨,如果有存貨,狀態就會轉換成無錢幣狀態,如果無存貨,便會轉換成已售完狀態。由此可知,機器中的狀態轉移,都是由機器本身自己判斷,並非人為操作,所以我們必須把狀態轉移的可能性都要預先設定在每個狀態之中,以達到目的。
State.png
  • Context:通常用來定義客戶感興趣的公開方法,同時維護一個實際處理目前狀態的實例物件。
  • State:狀態介面,用來封裝與Context的一個特定狀態所對應的行為。
  • ConcreteState:實作狀態處理的類別,每個類別實現一個跟Context相關的狀態。
interface State{
public void handle(Context context);
}
 
class ConcreteStateA implements State{
public void handle(Context context){
System.out.println("狀態A");
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State{
public void handle(Context context){
System.out.println("狀態B");
context.setState(new ConcreteStateA());
}
}
class Context{
private State state;
public void setState(State state){
this.state=state;
}
public void request(){
state.handle(this);
}
}
public class Client {
public static void main(String[] args) {
Context c=new Context();
c.setState(new ConcreteStateA());//設定初始狀態
c.request();//印狀態A
c.request();//印狀態B
c.request();//印狀態A
c.request();//印狀態B
}
}
  • 狀態模式使用單獨的類別來封裝一個狀態的處理,如果把一個大的程式控制分成很多小塊,每塊定義一個狀態來代表,那麼就可以把這些邏輯控制(if..else)分散到各個單獨的狀態類別,使程式更結構化和清晰。
  • 由於有了狀態處理的公共介面,所以擴充新的狀態(ConcreteStateA、B、C...)變的非常容易,只需要實現公共介面即可。

Simple Factory簡單工廠模式

  • 意圖:實作Api的方式可以有不同的實作方式(類別ImplA、B),Factory可以依條件選擇某一個實作類別來當做它的Api。
Simple factory.png
  • Api:定義客戶所需要的介面。
  • Impl:實作Api的實現類別,可能會有多個。
  • Factory:工廠,選擇合適的實現類別來建立Api物件。
interface Api{
public void operation(String s);
}
class ImplA implements Api{
public void operation(String s){
System.out.println("ImplA s=="+s);
}
}
class ImplB implements Api{
public void operation(String s){
System.out.println("ImplB s=="+s);
}
}
class Factory{
public static Api createApi(int condition){
Api api=null;
if(condition==1){
api=new ImplA();
}else if(condition==2){
api=new ImplB();
}
return api;
}
}
public class Client {
public static void main(String[] args) {
Api api=Factory.createApi(1);
api.operation("正在使用簡單工廠");
}
}
  • 簡單工廠模式實現了元件封裝使得用戶端(Client)、實作方式(ImplA、B、C...)的解耦。

Decorator裝飾模式

  • 意圖:讓一個物件(ConcreteComponent)可以動態的選擇增加哪些功能(ConcreteDecoratorA、B、C...)。就增加功能來說,裝飾模式比繼承來的更好。
  • 範例:你可以將此模式想像成每個人出門前都要打扮自己的感覺,每個人出門前都會按照自己的習慣順序穿上褲子、衣服、戴上手錶、穿上鞋子出門,這一連串的裝飾動作也就是意圖中提到的〝動態的選擇增加哪些功能〞,而人就是一個〝被裝飾者〞,衣物就是〝裝飾者〞。
Decorator.png
  • Component:定義一個物件的介面,可以給這些物件動態地增加功能。
  • ConcreteComponent:一個具體的物件,通常就是被裝飾的原始物件,也就是可以給這個物件增加功能。
  • Decorator:所有裝飾的抽象父類別,需要定義一個與Component一致的介面,並持有一個Component物件,也就是持有一個被裝飾者的物件。
  • ConcreteDecorator:具體的裝飾物件,實現實際要向被裝飾物件增加的功能。
abstract class Component{
public abstract String operation();
}
class ConcreteComponent extends Component{
public String description;
public String operation(){
return description="具體物件操作";
}
}
abstract class Decorator extends Component{
protected Component component;
public Decorator(Component component){
this.component=component;
}
public String operation(){
return component.operation();
}
}
class ConcreteDeoratorA extends Decorator{
public ConcreteDeoratorA(Component component){
super(component);
}
private String addedState="+裝飾A";
 
public String operation(){
return super.operation()+addedState;
}
}
class ConcreteDeoratorB extends Decorator{
public ConcreteDeoratorB(Component component){
super(component);
}
private String addedState="+裝飾B";
 
public String operation(){
return super.operation()+addedState;
}
}
public class Client {
public static void main(String[] args) {
Component c=new ConcreteComponent();
c=new ConcreteDeoratorB(c);
c=new ConcreteDeoratorA(c);
c=new ConcreteDeoratorA(c);
System.out.println(c.operation());
}
}
  • Client端呼叫c.operation()示意圖。
DecoratorCall.png
  • 從物件增加功能的角度來看,裝飾模式比繼承更為靈活。繼承是靜態的,一旦繼承,所有子類別都有一樣的功能;而裝飾模式是利用物件組合的方式,在執行時動態的組合功能。

Strategy策略模式

  • 意圖:定義一系列的演算法(ConcreteStrategyA、B、C...),把他們一個個封裝起來,可以透過Context物件內的Strategy變數使它們可以相互替換,此模式讓演算法的變化,不會影響到使用演算法的客戶。
Strategy.png
  • Strategy:策略介面,用來約束一系列實際的策略演算法(ConcreteStrategy),Context使用這個介面來呼叫實際的策略演算法。
  • ConcreteStrategy:實際的策略演算法。
  • Context:負責和實際策略類別互動,通常Context會持有一個真正的策略實現,還可以讓實際的策略類別獲得Context的資料。
interface Strategy{
public void algorithmInterface();
}
class ConcreteStrategyA implements Strategy{
public void algorithmInterface(){
System.out.println("實際的演算法實現A");
}
}
class ConcreteStrategyB implements Strategy{
public void algorithmInterface(){
System.out.println("實際的演算法實現B");
}
}
class ConcreteStrategyC implements Strategy{
public void algorithmInterface(){
System.out.println("實際的演算法實現C");
}
}
class Context{
private Strategy strategy;
public Context(Strategy aStrategy){
this.strategy=aStrategy;
}
public void contextInterface(){
strategy.algorithmInterface();
}
}
public class Client {
public static void main(String[] args) {
Strategy strategyA=new ConcreteStrategyA();
Context ctx1=new Context(strategyA);
ctx1.contextInterface();
 
Strategy strategyB=new ConcreteStrategyB();
Context ctx2=new Context(strategyB);
ctx2.contextInterface();
}
}

Bridge橋接模式

  • 意圖:將抽象部分(Abstraction)與他的實現部分(Implementor)分離,使它們都可以獨立變化。
Bridge.png
  • Abstraction:抽象部分的類別,通常在這個類別中,會持有一個實現部分(Implementor)的物件,裡面的方法,需要呼叫實現部分的物件來完成。
  • RefinedAbstraction:擴充抽象部分的公開方法,通常在這類別中,定義跟實際業務相關的方法,這些方法的實現通常會使用Abstraction中定義的方法,也可能需要呼叫實現部分的物件來完成。
  • Implementor:實現部分的介面,這個介面不用和Abstraction中的方法一致,通常是由Implementor介面提供基本的操作,而Abstraction中定義的是基於這些基本操作的較高階層的操作。
  • ConcreteImplementor:真正實現Implementor介面的類別。
interface Implementor{
public void operationImpl();
}
abstract class Abstraction{
protected Implementor impl;
public Abstraction(Implementor impl){
this.impl=impl;
}
public void operation(){
impl.operationImpl();
}
}
class ConcreteImplementorA implements Implementor{
public void operationImpl(){
System.out.println("具體實現A方法執行");
}
}
class ConcreteImplementorB implements Implementor{
public void operationImpl(){
System.out.println("具體實現B方法執行");
}
}
class RefinedAbstraction extends Abstraction{
public RefinedAbstraction(Implementor impl){
super(impl);
}
public void otherOperation(){
 
}
}
public class Client {
public static void main(String[] args) {
Implementor impl=new ConcreteImplementorA();
Abstraction m=new RefinedAbstraction(impl);
m.operation();
}
}
  • 抽象部分和實現部分都可以分別變化擴充,完美的實現開放-封閉原則(OCP)。
  • 註:開放-封閉原則(OCP), 一個類別應該對擴充開放,對修改關閉。
  • 實現了多用物件組合,少用物件繼承的觀念,因為使用物件繼承來擴充功能,大大增加了物間間的耦合性。

Adapter轉接器模式

  • 意圖:將一個類別(Adaptee)的公開方法(public method)轉換成客戶希望的另一個類似但不完全一樣的方法,這個方法定義於Target中。轉接器模式(Adapter)使得原本介面不相容而無法一起工作的那些類別可以一起工作。
  • 範例:你可以將此模式想像成〝電器轉接頭〞,每個國家的插座和電壓都不太一樣,當你出國需要充電或需要用到自備的電器用品時,你就需要一個轉接頭,也就是這個模式的adapter類別,你的電器插頭就像是Target介面,而國外的插座就是Adaptee類別,要讓你的電器正常運作,轉接頭就扮演著非常重要的角色。
Adapter.png
  • Target:這是客戶端(Client)所期待的介面,目標可以是具體的或抽象的類別,也可以是介面。
  • Adaptee:定義於Adaptee中的公開方法(public method),通常能滿足用戶端的功能需求,但是此公開方法與用戶端所期待的(Target)不一致,需要被轉換。
  • Adapter:轉接器,把Adaptee轉換成為客戶端需要的Target。
interface Target{
public void request();
}
class Adaptee{
public void specificRequest(){
System.out.println("轉接成功");
}
}
class Adapter implements Target{
private Adaptee adaptee=new Adaptee();
public void request(){
adaptee.specificRequest();
}
}
public class Client {
public static void main(String[] args) {
Target target=new Adapter();
target.request();
}
}
  • 更好的重用性,若功能是已經有的,只是介面不相容,透過轉接器模式就能讓這些功能重複使用。

參考文獻

Personal tools