設計模式精髓篇之行為型
行為型模式
主要是對物件的行為進行設計,解決物件間的聯絡問題也可以理解為解決物件與物件間的通訊。
續前3篇
模板方法模式
目標:
定義一個演算法操作框架,將一些步驟延遲到子類中,使得子類可以不改變整個演算法的操作的順序操作結構但可以重新定義演算法中特定的步驟的具體實現。
why-思考:
當一個完整的演算法操作流程的結構是固定的,但是其中一些特定的步驟的具體實現是多變的,如何在固定的步驟中,分化出多變的實現,提升演算法的可擴充套件性?
how-思考:
將多變的實現部分給抽取出來,先整體把握整個演算法的思路流程。
JDK中的示例:
InputStream 類的read方法
public abstract class InputStream implements Closeable {
//1.定義出你的大體的演算法步驟,其中read()函式是步驟之一,但沒具體實現,因為該函式內的實現是多變的
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();//待具體實現,這已經屬於是一個固定的演算法操作流程中的步驟
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
//2.抽取出要具體實現的部分
public abstract int read() throws IOException;
}
//3.交給子類去具體實現
public class FileInputStream extends InputStream
{
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;//呼叫的是Native方法,c庫中的方法。使用的是JNI機制
}
迭代器模式
目標:
提供一種方法順序訪問一個聚合物件中各個元素, 而又無須暴露該物件的內部表示。
why-思考:
怎麼做到能在不暴露物件內部的情況下又能順利的訪問到聚合物件中的各個元素呢?
how-思考:
要想做到不暴露物件的內部情況,首先就是不能直接通過調要組合物件的方法來訪問組合物件中的各個元素。此時,應想到代理的方式,將訪問組合物件的各個元素的方法抽象出去成為一個代理物件。
JDK中的示例:
java.util.Iterator
//1.抽象出來的訪問聚合物件的各個元素的方法的介面
public interface Iterator<E> {
public boolean hasNext();
public boolean hasNext();
public void remove();
}
//2.具體實現該操作的介面函式
private class ArrayListIterator implements Iterator<E> {
ArrayList<E> ourList = ArrayList.this;//需關聯到要遍歷的組合物件
......//具體實現的介面方法
}
//3.具體的聚合物件ArrayList 需對外提供生成 操作各個元素的介面。
//ArrayList類中包括該方法
public Iterator<E> iterator() {
return new ArrayListIterator();
}
觀察者模式
目標:
當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。是一個觀察者和被觀察者的一個物件依賴關係
why-思考:
因為所有的觀察者可能各不一樣,怎麼將觀察者統一起來,並依賴到觀察者中,這是一個難點。當被觀察者的狀態發生改變時,怎麼做到即時通知依賴它的觀察者並更新?
how-思考:
首先得將觀察者統一起來,都統一實現同一介面即可統一,然後,提取出觀察者觀察到狀態改變時需要更新的公共部分。
JDK中的示例:
EventListener
//1.JDK為觀察者提供的統一介面
public interface EventListener {
/* empty */
}
//2.擴充套件統一介面
public interface MyEventListener extends EventListener {
public void update();
}
//3.觀察者實現統一介面,可以定義類A,類B等不同的觀察者
public class A implements MyEventListener {
Subject subject;//需要關聯的被觀察者物件
//3.1將觀察者依賴到被觀察者上
public A(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
//3.2被觀察者狀態改變後的更新操作
@Override public void update() {
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
//4.定義被觀察者
public class Subject {
private List<MyEventListener> observers = new ArrayList<MyEventListener >();
//4.1依賴到觀察者
public void attach(Observer observer){
observers.add(observer);
}
//4.2狀態改變,通知觀察者
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
責任鏈模式
目標:
描述的是一個請求與多個請求處理者的一個關係。多個處理者由每一個處理者對其下家的引用而連線起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。此時,哪個處理者處理該請求由執行時刻自動確定,或者可以動態指定哪個處理者處理請求。
why-思考:
如何將多個處理者通過引用連線起來?如何新增處理者?如何動態的指定處理者去處理請求。
how-思考:
處理者間需要以連結串列的形式,包含指向下個處理者的引用。新增處理者可以指向下個處理者,也可以不指向,不指向的時候表示處理者鏈到結尾了。通過抽象出處理者中共同的處理函式來使處理者由不同的處理方式。
JDK中的示例:
java web中的javax.servlet.Filter
//方式一,實現介紹常規例子
//1.設定抽象處理者,並將處理者與下個處理者串聯起來
abstract class AbHandler {
private AbHandler nextHandler;
public void setNextHandler(AbHandler nextHandler){
this.nextHandler=nextHandler;
}
public AbHandler getNetHandler(){
return nextHandler;
}
abstract public void doHandler();
}
//2.具體處理者的實現
public class ConcernHandler{
void doHandler(){
//2.1決定具體實現不實現
......
//2.2處理事件繼續下去
if(getNetHandler!=null){
getNetHandler().doHandler();
}
}
}
//方式二 javax.servlet.Filter處理者--------------
//一般定義Filter需要在web.xml配置中進行,可以定義多個
public class AFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
.........//處理者處理
//chain為處理鏈,這裡它不是使用的Filter處理者依賴下一個處理者Filter的方式,而是使用原始碼中具體實現FilterChain的介面的類中陣列管理Filter的方式來依賴下一個
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
策略模式
目標:
一個類的行為或其演算法可以在執行時更改。
why-思考:
如何做到類中的行為可更改。類中的行為可理解為類中的函式。
how-思考:
面向介面程式設計,實現介面或者抽象函式可實現多變性。
JDK中的示例:
javax.servlet.http.HttpServlet
//分兩種情況
//情況一,(不是JDK中的例子)。通過實現SerMethod 介面,可以有多個不同的行為。
public class A{
//定義可多變性的多型介面成員物件。省略了基本的set,get方法
SerMethod serMethod;
public void method1(){
serMethod.dothing()
}
}
public interface SerMethod{
public void dothing();
}
//情況二
//其中HttpServletRequest和HttpServletResponse是介面型別,因此該doGet方法可以因為實現上面兩個介面構造出不同的物件,來改變doGet內部的行為。
public abstract class HttpServlet extends GenericServlet{
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if(protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
}
//總之,面向介面程式設計,多變的進行封裝成介面
命令模式
目標:
為了解決命令的請求者和命令的實現者之間的耦合關係。一方面,方便的對命令進行擴充套件;另一方面,對多個命令的統一控制。
why-思考:
如何實現請求者和命令的實現者的解耦?如何實現對命令的控制?
how-思考:
首先出現解耦,就會想到使用介面,因為介面可以使類與類之間的強耦合關係降低。定義一個命令介面,這樣可以實現對多個命令的擴充套件,也可以實現解耦。
JDK中的示例:
java.lang.Runnable(命令)
//1.定義命令介面
public interface Runnable {
public abstract void run();
}
//2.定義接收者-如果接收者有多個且包含共同特徵則定義介面,否則直接定義一個類即可
public interface IReceiver {
public abstract void doSomeThing();
}
//3.定義具體的命令,並關聯命令的接受者。
public class ConcreteThread implements Runnable{
private IReceiver ireceiver;//此出省略set和get方法
@Override
public void run(){
.....
ireceiver.doSomeThing();
......
}
}
//4.定義命令的請求者
public class Invoker
{
//4.1需要關聯傳送的命令,通過關聯了命令,實現與接受者IReceiver 解耦
private ICommand commandA = null;
public void SetCommandA(ICommand commandA)
{
this.commandA = commandA;
}
//4.2執行命令A---對命令的控制
public void RunCommandA()
{
commandA.Execute();
}
//5具體實現
Invoker invoker=new Invoker ();
invoker.SetCommandA(new ConcreteThread (new ConcreteReceiver()))
invoker.RunCommandA();
狀態模式
目標:
物件中的內建狀態改變時相應的允許改變其行為。
why-思考:
怎麼將物件的內部物件和其行為聯絡起來?
how-思考:
聯絡起來並不是簡單的將狀態作為物件的內部方法的形參這麼簡單,這樣會使得物件的那個內部方法顯得很臃腫,應該將狀態和狀態改變的行為封裝到一塊。
普通示例:
//1.首先定義狀態和狀態行為。
public interface State{
//狀態行為
public void dothing();
}
//2.有狀態的物件的類
public void Context{
private State state;//省略了基本的set,get方法
public void test(){
state.dothing();//根據狀態做狀態內對應的狀態行為
}
}
備忘錄模式
目標:
在不破壞封裝的條件下,將一個物件的狀態捕捉住,並外部化,儲存起來,從而可以在將來合適的時候把這個物件還原到儲存起來的狀態。
why-思考:
怎麼保證不破壞封裝的情況下,不被其他物件讀取?這是重點
how-思考:
首先將需要備忘的內容交給唯一管理物件類備忘錄角色。再將備忘錄角色交給總負責人建立,總負責人不對備忘錄物件裡的備忘內容檢視。
示例:
//1.定義一個發起人角色(需要備忘的物件),負責建立一個含有當前的內部狀態的備忘錄物件,使用備忘錄物件儲存自身內部的狀態。
public class Originator {
private String state;//需要備忘的值,省略了基本的set,get方法
//1.1建立備忘錄物件
public Memento createMemento(){
return new Memento(state);
}
//根據備忘錄物件的狀態恢復狀態
public void restoreMemento(Memento memento){
this.state = memento.getState();
}
}
//2.定義一個備忘錄角色類,負責管理需要備忘的值,相當於Originator 的備忘的內容得代理
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
//3.定義一個總負責人的角色,負責儲存備忘錄物件,這可以需要做到不檢視備忘內容。
public class Caretaker {
private Memento memento;
// 備忘錄的取值方法
public Memento retrieveMemento(){
return this.memento;
}
//備忘錄的賦值方法
public void saveMemento(Memento memento){
this.memento = memento;
}
}
訪問者模式
目標:
封裝一些施加於某種資料結構元素之上的操作。一旦這些操作需要修改的話,接受這個操作的資料結構則可以保持不變。
why-思考:
如何做到將資料結構和資料操作進行分離,或者說解耦?
how-思考:
在被訪問的類(代表資料結構)裡面加一個對外提供接待訪問者(代表資料操作)的介面。
示例:
//1.定義資料結構(被訪問的類)裡面需要包含接收統一的資料操作(訪問者)的方法。
public abstract class Employee {
// 接收/引用一個抽象訪問者物件 ,department為抽象的資料操作(訪問者)
public abstract void accept(Department department);
}
//1.1具體的資料結構即被訪問者,可以有多個被訪問者
public class ConcreteEmployee extends Employee {
......//資料結構的基本成員變數
@Override
public void accept(Department department) {
department.visit(this);
}
}
//2.定義資料操作(訪問的類),包含對每種具體的被訪問者(資料結構)宣告一個訪問操作
public abstract class Department {
......//訪問者操作的多個數據結構,因為被訪問者可以有多個
public abstract void visit(ConcreteEmployee me);
public abstract void visit(ConcreteEmployee2 me);
.....
}
//2.1定義具體的資料操作,訪問者
public class ConcreteDepartment extends Department {
@Override
public void visit(ConcreteEmployee me) {
.....//具體操作ConcreteEmployee 中的資料結構的
}
}
//3.具體實現
Employee el=new ConcreteEmployee ();//3.1定義資料結構(被訪問者)
Department dm=new ConcreteDepartment ();//3.2定義具體的資料操作(訪問者)
//3.3定義資料操作接收的資料操作,accpet函式可以接收不同的資料操作,但是el資料結構是不變的
el.accept(dm);
感想
1.不同模組和操作角色間的最好解耦方式,就是將容易多變的部分介面化,**面向介面程式設計**。
2.物件的行為(方法)要麼是依賴於其內部成員變數,要麼是依賴於外部的其他物件。如果該物件的行為會因為依賴的物件或者因為系統要進行擴充套件而不唯一時,此時應該將依賴的物件的類進行抽象,利用多型的方式依賴於該物件。
3.**多變的部分一定得進行封裝,抽象化**