设计模式


前言

OO基础

  • 抽象
  • 封装
  • 多态
  • 继承

OO原则

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程
  • 为交互对象之间的松耦合设计而努力
  • 类应该对扩展开放,对修改关闭
  • 要依赖抽象,不要依赖具体类(依赖倒置原则)
  • 只和朋友交谈(最少知识原则,减少对象之间的交互)
  • 父类:别调用我们,我们会调用你(好莱坞原则)
  • 一个类应该只有一个引起变化的原因(设计原则)

1. 策略模式

定义算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

public interface IBehavior {
    void toDo(); // 行为
}
public class Test {
    private IBehavior behavior;

    // 设置行为
    public void setBehavior(IBehavior bhv) {
        behavior = bhv;
    }

    // 执行行为
    public void toDoSomething() {
        behavior.toDo();
    }
}

2. 观察者模式

在对象之间定义一对多的依赖。当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。

// 被观察者
public interface Subject {
    public void addObserver(Observer obj);
    public void removeObserver(Observer obj);
    public void notifyObservers();
}

// 观察者
public interface Observer {
    public void update(Subject obj, Object arg);
}

public class TestData : Subject {
    private List<Observer> observers;

    // 构造函数
    public void TestData() {
        observers = new List<Observer>();
    }

    // 添加观察者
    public void addObserver(Observer obj) {
        observers.Add(obj);
    }

    // 移除观察者
    public void removeObserver(Observer obj) {
        if (observers.Exists(obj)) {
            observers.Remove(obj);
        }
    }

    // 通知观察者
    public void notifyObservers() {
        foreach (Observer obj in observers) {
            obj.update(this, null);
        }
    }
}

3. 装饰者模式

动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。

// 基础类
public abstract class BaseObj {
    protected string desc = "base";

    public virtual string getDeac() {
        return desc;
    }

    public abstract int getID();
}

// 测试类
public class TestObj : BaseObj {
    public TestObj() {
        desc = "test"; 
    }

    public override int getID() {
        return 233;
    }
}

// 修饰实现类
public class DecorateTestObj : BaseObj {
    private BaseObj baseObj;

    public DecorateTestObj(BaseObj baseObj) {
        this.baseObj = baseObj; 
    }

    public override string getDeac() {
        return "Decorate[" + baseObj.getDeac() + "]";
    }

    public override int getID() {
        return baseObj.getID() * 10;
    }
}

class Program
{
    static void Main(string[] args)
    {
        TestObj testObj = new TestObj();
        Console.WriteLine("Desc: {0}, ID: {1}.", testObj.getDeac(), testObj.getID());

        // 进行装饰
        DecorateTestObj newTestObj = new DecorateTestObj(testObj);
        Console.WriteLine("Desc: {0}, ID: {1}.", newTestObj.getDeac(), newTestObj.getID());
        Console.ReadLine();
    }
}

4. 工厂模式

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个(即,将实例化对象的抽象方法的实现内容,由子类来实现)。工厂方法让类把实例化推迟到子类。

abstract Product factoryMethod(string type) // 实例化对象的抽象方法

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂的方法经常以工厂方法的方式实现,而抽象工厂的任务只是定义一个负责创建一组产品的接口。

5. 单件模式

单件模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。

public class Singleton {
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
}

注意:多线程的处理问题(加锁?)。

6. 命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

public interface Command {
    void execute();
    void undo();
}

public class NoCommand : Command {
    public execute() {}
    public undo() {}
}

public class RemoteControlWithUndo {
    Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;

    public int commandCount = 3;

    public RemoteControlWithUndo() {
        onCommands = new Command[commandCount];
        offCommands = new Command[commandCount];

        Command noCommand = new NoCommand();
        for (int i = 0; i< commandCount; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }

    public void setCommand(int index, Command onCommand, Command offCommand) {
        onCommands[index] = onCommand;
        offCommands[index] = offCommand;
    }

    public void onBtnClick(int index) {
        onCommands[index].execute();
        undoCommand = onCommands[index];
    }

    public void offBtnClick(int index) {
        offCommands[index].execute();
        undoCommand = offCommands[index];
    }

    public void undoBtnClick() {
        undoCommand.undo();
    }
}

宏命令:初始化时传入多组命令,当执行宏命令时,就会一次性执行所传入的所有命令。

public class MacroCommand : Command {
    Command[] commands;

    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    public void execute() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].execute();
        }
    }
}

应用场景:

  • 工作队列:在某一端添加命令,然后在另一端(可以是另一个线程)中弹出一个命令,进行调用(execute)。
  • 日志记录:将所有动作命令都记录在日志中,并能在系统死机之后,重新加载这些动作,通过依次调用动作的execute()方法,恢复到出错的状态,从而快速查找到问题。为了减少日志的记录量,可以定义一些检查点,只将上次检查点之后的所有操作记录下来。

7. 适配器模式

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

// 两个不同的类
public interface Target {
    void play();
}
public interface Source {
    void hit();
}

// 适配器
public class Adapter : Target {
    Source src;

    public Adapter(Source src) {
        this.src = src;
    }

    public void play() {
        src.hit();
    }
}

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

// 两个子系统
public interface SubSys1 {
    void play();
}
public interface SubSys2 {
    void hit();
}

public class Appearance {
    SubSys1 sys1;
    SubSys2 sys2;

    public Appearance(SubSys1 sys1, SubSys2 sys2) {
        this.sys1 = sys1;
        this.sys2 = sys2;
    }

    // 统一play接口
    public void play() {
        sys1.play();
        sys2.hit();
    }
}

最少知识原则要求在对象的方法中,只应调用以下范围的方法:

  • 该对象本身;
  • 被当作方法的参数而传递进来的对象;
  • 此方法所创建或实例化的任何对象;
  • 对象的任何组件(指被实例变量所引用的任何对象)。
// 不推荐以下方式
public float getTestVal() {
    Value valObj = valPool.GetValue("Test");
    return valObj.Get();
}

// 推荐这种方式
public float getTestVal() {
    return valPool.GetTestValue(); // 直接在valPool中实现GetTestValue方法来获取Test的值。以减少所依赖类的数量。
}

模式意图

  • 装饰者 -> 不改变接口,但加入责任
  • 适配器 -> 将一个接口转成另一个接口
  • 外观 -> 让接口更简单

8. 模板方法模式

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

abstract class AbstractClass {
    // 模板方法
    public sealed override void templateMethod() {
        primitiveOp1();
        primitiveOp2();
        concreteOp();
        hook();
    }

    public abstract void primitiveOp1(); // 子类实现

    public abstract void primitiveOp2(); // 子类实现

    // 抽象类的具体操作
    public sealed override void concreteOp() {
        // 抽象类的实现
    }

    // 钩子方法
    public virtual void hook() {}
}

好莱坞原则:将决策权放在高层模块中,以便决定如何及合适调用底层模块。

策略模式模板方法模式都封装算法,一个用组合,一个用继承。
工厂方法使模板方法的一种特殊版本。

配对模式与叙述:

  • 策略 -> 封装可互换的行为,然后使用委托来决定要采用哪一个行为
  • 工厂方法 -> 由子类决定实例化哪个具体类
  • 模板方法 -> 子类决定如何实现算法中的某些步骤

9. 迭代器与组合模式

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

public interface Iterator {
    bool hasNext();
    Object next();
}

当一个模块或一个类被设计成只支持一组相关的功能时,就说其具有高内聚;反之,当被设计成支持一组不相关的功能时,就说其具有低内聚。

组合模式允许将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

public abstract class Component {
    ArrayList components = new ArrayList();

    public abstract void add(Component component);
    public abstract void remove(Component component);
    public abstract void getChild(int i);
    public virtual Iterator createIterator() {
        return new CompositeIterator(components.iterator());
    };
}

// 组合迭代器
public class CompositeIterator : Iterator {
    Stack stack = new Stack();

    // 将顶层组合的迭代器(相当于树的根节点)传入
    public CompositeIterator(Iterator iterator) {
        stack.push(iterator);
    }

    public Object next() {
        if (hasNext()) {
            Iterator iterator = (Iterator) stack.peek();
            Component component = (Component) iterator.next();
            stack.push(component.createIterator());
            return component;
        } else {
            return null;
        }
    }

    public bool hasNext() {
        if (!stack.empty()) {
            Iterator iterator = (Iterator) stack.peek();
            if (!iterator.hasNext()) {
                stack.pop();
                return hasNext();
            } else {
                return true;
            }
        }
        return false;
    }
}

模式描述

  • 策略 -> 封装可交换的行为,并使用委托决定使用哪一个
  • 适配器 -> 改变一个或多个类的接口
  • 外观 -> 简化一群类的接口
  • 迭代器 -> 提供一个方式来遍历集合,而无须暴露集合的实现
  • 组合 -> 客户可以将对象的集合以及个别的对象一视同仁
  • 观察者 -> 当某个状态改变时,允许一群对象能被通知到

10. 状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

public interface IState {
    void handle(); // 所有具体状态的共同接口
}

public class ConcreteStateA : IState {
    public void handle() {
        // 实现行为
    }
}

public class ConcreteStateB : IState {
    public void handle() {
        // 实现行为
    }
}

// 状态常量
public enum State {
    A, B,
}

public class Context {
    IState stateA;
    IState stateB;

    IState state = stateA; // 默认状态A

    // 允许切换状态
    public void SetState(State state) {
        switch(state) {
            case State.A:
                this.state = stateA;
                break;
            case State.B:
                this.state = stateB;
                break;
            default:
                System.out.println("Invalid state !");
                break;
        }
    }

    public void request() {
        state.handle();
    }
}

模式描述

  • 状态 -> 封装基于状态的行为,并将行为委托到当前状态
  • 策略 -> 将可以互换的行为封装起来,然后使用委托的方法,决定使用哪一个行为
  • 模板方法 -> 由子类决定如何实现算法中的某些步骤

11. 代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

使用代理模式创建代表对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

public interface Subject {
    void request();
}

public class RealSubject {
    public void request() {
        // 实现行为
    }
}

public class Proxy : Subject {
    RealSubject subject;

    public void request() {
        // 执行其他逻辑
        // ...

        // 调用所控制的对象
        subject.request();
    }
}

代理模式由许多变体,例如:缓存代理、同步代理、防火墙代理和写入时复制代理。

12. 其他模式

桥接

在改变实现的同时,也改变抽象。

生成器

封装一个产品的构造过程,并允许按步骤构造。

责任链

让一个以上的对象有机会能够处理某个请求。

蝇量

让某个类的一个实例能用来提供许多“虚拟实例”。

解释器

为语言创建解释器。

中介者

集中相关对象之间复杂的沟通和控制方式。

备忘录

让对象返回之前的状态(如,撤销)。

原型

适用于创建给定类的实例的过程很昂贵或很复杂的情况。

访问者

为一个对象的组合增加新的能力,且封装并不重要。

results matching ""

    No results matching ""