设计模式

有些人已经解决你的问题了,以往是代码复用,现在是经验复用

策略模式

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

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起

这几乎是每个设计模式背后的精神所在 针对接口编程,而不是针对实现编程 我们利用接口代表每个行为,而行为的每个实现都将实现其中的一个接口 针对接口编程真正意思是**针对超类型(superType)**编程 我们有一个接口和它们对应的类来负责实现具体的行为

interface FlyBehavior {
  fly() {
    // 必须实现 quack 方法用以调用
  }
}

通过它实现一个行为类

class FlyWithWings implyments FlyBehavior {
  fly () {
    // 实现特定的行为类
  }
}

在需要调用的类上调用这个行为

public class Duck {
  FlyBehavior flyBehavior;

  // 随时动态设置飞行类
  public void setFlyBehavior(FlyBehavior fb) {
    flyBehavior = fb
  }

  performFly() {
    // 直接调用行为类
    flyBehavior.fly()
  }
}

再使用特定类继承

public class MallarDuck extends Duck {
  public MallardDuck () {
    flyBehavior = new FlyWithWings()
  }
}

多用组合,少用继承

使用组合建立系统具有很大的弹性,不经可以将算法族封装成类,更可以在运行时动态地改变行为

观察者(Observer)模式

出版者 + 订阅者 = 观察者模式

为了交互对象之间的松耦合设计而努力

松耦合设计之所以能让我们建立有弹性的 OO 系统,能够应对变化,是因为对象之间的互相依赖降到了最低

首先实现主题接口

public interface Subject {
  public void registerObserver(Observer o);
  public void removeObserver(Observer o);
  public void notifyObservers();
}

public interface Observer {
  public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
  public void display();
}

使用观察者模式实现主题接口

public class WeatherData implements Subject {
  private ArrayList observers;
  private float temperature;

  public WeatherData() {
    observers = new ArrayList();
  }

  public void registerObserver(Observer o) {
    obervers.add(o)
  }

  public void removeObserver(Observer o) {
    int i = observers.indexOf(o);
    if (i > 0) {
      observers.remove(i);
    }
  }

  public void notifyObservers() {
    for (int i = 0; i < observers.size(); i++) {
      Observer observer = (Observer) observers.get(i);
      observer.update(temperature, humidity, pressure);
    }
  }

  public void measurementsChanged() {
    notifyObservers();
  }

  public void setMeasureMents(float temperature) {
    this.temperature = temperature;
    measurementsChanged();
  }
}

再实现一个订阅者

public class CurrentConditionsDisplay implements Observer, DisplayElement {
  private float temperature;
  private Subject weatherData;

  public CurrentConditionsDisplay(Subject weatherData) {
    this.weatherData = weatherData;
    weatherData.registerObserver(this);
  }

  public void update(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    display();
  }

  public void display() {
    // ...;
  }
}

测试

WeatherData weatherData = new WeatherData();

CurrentConditionDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

weatherData.setMeasurements(80);

装饰者模式

类应该对扩展开放,对修改关闭

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为

装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。比如我们想要个摩卡奶泡咖啡,可以以摩卡 Mocha ,奶泡 Whip 装饰它

public abstract class Beverage {
  String description = "Unknown Beverage";

  public String getDescription() {
    return description;
  }

  public abstract double cost();
}

再实现装饰者抽象类

public abstract class CondimentDocorator extends Beverage {
  public abstract String getDescription();
}

实现被装饰类,

public class Espresso extends Beverage {
  public Espresso() {
    description = "Espresso";
  }

  public double cost() {
    return 1.99;
  }
}

实现装饰者类

public class Mocha extends CondimentDecorator {
  // 用一个实例记住被装饰者
  Beverage beverage;

  Mocha(Beverage beverage) {
    this.beverage = beverage;
  }

  public String getDescription() {
    return beverage.getDescription() + ", Mocha";
  }

  public double cost() {
    return .20 + beverage.cost();
  }
}

测试

Beverage beverage = new Espresso();
beverage2 = new Mocha(beverage);
beverage2.getDescription();

工厂模式

除了使用 new 操作符之外,还有更多制造对象的方法。工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

依赖倒置原则 要依赖抽象,不要依赖具体类

想要遵循依赖倒置原则,工厂方法并非是唯一的技巧,但却是最有威力的技巧之一

有几个指导方针可以帮你避免在面向对象设计中违反依赖倒置原则

  • 变量不可以持有具体类的引用
  • 不要让类派生自具体类,应该派生自一个抽象类或是接口
  • 不要覆盖基类中已实现的方法,如果覆盖基类已实现的方法,那么基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享
public abstract class PizzaStore {
  public Pizza orderPizza(String type) {
    Pizza pizza;

    pizza = createPizza(type);

    pizza.prepare();
    pizza.bake();
  }

  // 我们可以让披萨制作活动局限于 PizzaStore 类
  abstract Pizza createPizza(String type);
}

允许子类做决定

public class NYPizzaStore extends PizzaStore {
  Pizza createPizza(String item) {
    if (item.equas("cheese")) {
      return new NYStyleCheesePizza();
    } else if (item.equals("veggie")) {
      return new NYStyleClamPizza();
    }
  }
}

当有披萨店时也需要提供披萨

public abstract class Pizza {
  String name;
  String dough;

  void prepare() {
    // 准备
  }

  void bake() {
    // 烘烤
  }
}

public class NYStyleCheesePizza extends Pizza {
  public NYStyleCheesePizza() {
    name = "NY style Pizza";
    douph = "Thin Crust Dough";
  }
}

测试

PizzaStore nyPizzaStore = new NYPizzaStore();
nyPizzaStore.orderPizza("cheeze");

单件模式

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

其实有些对象我们只需要一个,比如线程池 threadpool 、缓存 cache 、对话框、处理偏好和注册表等

public class Singleton {
  private static Singleton uniqueInstance;

  private Singleton() {
    // 把构造器声明为私有,这样只有自 Singleton 类才可调用
  }

  public static Singleton getInstance () {
    if (uniqueInstance == null) uniqueInstance = new Singleton();
    return uniqueInstance;
  }
}

在 java 中,可在 getInstance 方法中加上 synchronized 关键字就可在多线程模式下迫使每个线程在进入这个方法之前,要等候别的线程离开该方法

命令模式

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

我们需要实现一个命令接口,之后的所有命令对象都要实现它

public interface Command {
  public void execute();
  // 撤销
  public void undo();
}

public class LightOnCommand implements Command {
  Light light;

  public LightOnCommand(Light light) {
    this.light = light;
  }

  public void execute() {
    light.on();
  }

  public void undo() {
    light.off();
  }
}

public class LightOffCommand implements Command {
  Light light;

  public LightOffCommand(Light light) {
    this.light = light;
  }

  public void execute() {
    light.off();
  }

  public void execute() {
    light.on();
  }
}

// NoCommand 是一个空对象的例子。当你不想返回一个有意义的对象时,空对象就有用。
public class NoCommand implements Command {
  public void execute() {}
}

// 宏命令
public class MacroCommand implements Command {
  Command[] commands;

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

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

接下来我们需要使用命令对象

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

  public RemoteControl() {
    onCommands = new Command[7];
    offCommands = new Command[7];

    Command noCommand = new NoCommand();

    for(int i = 0; i < 7; i++) {
      onCommands[i] = noCommand;
      offCommands[i] = noCommand;
    }
  }

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

  public void onButtonWasPushed(int slot) {
    onCommands[slot].execute();
    undoCommand = onCommands[slot];
  }

  public void offButtonWasPushed(int slot) {
    offCommands[slot].execute();
    undoCommand = offCommands[slot];
  }

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

测试

public class RemoteLoader() {
  public static void main(String[] args) {
    RemoteControl remoteControl = new RemoteControl();

    Light livingRoomLight = new Light("Living Room");

    LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);

    remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
    remote.onButtonWasPushed(0);
    remote.offButtonWasPushed(0);

    // 测试宏命令
    Command[] partyOn = { lightOn, tvOn }
    Command[] partyOff = { lightOff, tvOff }
    remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
  }
}

命令还有更多用途

  1. 工作队列,比如线程池、日程安排、工作队列等。从队列中取出一个命令,调用它的execute()方法
  2. 日志请求,将所有动作记录在日志中,并在系统死机之后,重新调用这些动作恢复到之前的状态,通过新增两个方法store()load()

适配器模式与外观模式

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

// 将枚举是配成迭代器,适配器需要实现迭代器接口
public class EnumberationIteratior implements Iterator {
  Enumeration enum;

  public EnumerationIteration(Enumeration enum) {
    this.enum = enum;
  }

  public boolean hasNext() {
    return enum.hasMoreElements();
  }

  public Object next() {
    return enum.nextElement();
  }
  // 枚举器不能实现迭代器的溢出方法
  public void remove() {
    throw new UnsupportedOperationException();
  }
}

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

最少知识原则:只和你的密友谈话

这个原则提供了一些仿真:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:

  • 该对象本身
  • 被当做方法的参数而传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件
public class Car {
  Engine engine;

  public Car() {
    // 初始化发动机
  }

  public void start(Key key) {
    Doors doors = new Doors();

    // 参数的方法可以调用
    boolean authorized = key.turns();

    if (authorized) {
      // 可以调用对象组件的方法
      engine.start();
      // 调用同一对象本地方法
      updateDashboardDisplay();
      // 调用所创建的实例的方法
      doors.lock();
    }
  }

  public void updateDashboardDisplay() {
    // 更新显示
  }
}

模板方法模式

模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现

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

先实现一个抽象类

public abstract class CaffeineBeverageWithHook {
  void prepareReciope() {
    boilWater();
    brew();
    addCondiments();

    // 子类通过覆写抽象方法来实现模板方法
    // 生命周期就是这种模式
    abstract void brew();
    abstract void addCondiments();

    void boilWater() {
      // do something
    }
  }
}

好莱坞原则 别调用我们,我们会调用你

这种原则防止高层组件以来低层组件,而底层组件又依赖高层组件,这样会没有人能轻易搞懂系统是如何设计的

迭代器与组合模式

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

一个类应该只有一个引起变化的原因

当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚

组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合 组合模式以单一责任设计原则换取透明性。这样,客户就可以将组合和叶节点一视同仁

public class CompositeIterator implements Iterator {
  Stack stack = new Stack();

  public CompositeIterator(Iterator iterator) {
    stack.push(iterator);
  }

  public Object next() {
    if (hasNext()) {
      // peek 是把第一项复制并拿出
      Iterator iterator = (Iterator) stack.peek();
      MenuComponent component = (MenuComponent) iterator.next();
      if (component instanceof Menu) {
        stack.push(component.createIterator());
      }
      return component;
    } else {
      return null;
    }
  }

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

  public void remove() {
    throw new UnsupportedOperationException();
  }
}

public class NullIterator implements Iterator() {
  public Object next() { return null; }

  public boolean hasNext() { return false; }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}
// 菜单组件视为叶节点和组合节点提供一个共同的接口
public abstract class MenuComponent {
  public void add(MenuComponent menuComponent) { throw new UnsupportedOperationException(); }
  public void remove(MenuComponent menuComponent) { throw new UnsupportedOperationException(); }
  public MenuComponent getChild(int i) { throw new UnsupportedOperationException(); }

  public string getName() { throw new UnsupportedOperationException(); }
  public void print() { throw new UnsupportedOperationException(); }

  public Iterator createIterator() { throw new UnsupportedOperationException(); }
}

// 菜单组件和菜单橡都为同一抽象类
public class MenuItem {
  String name;

  public MenuItem(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void print() {
    System.out.print(" " + getName());
  }

  public Iterator createIterator() {
    return new NullIterator();
  }
}

public class Menu extends MenuComponent {
  ArrayList menuComponents = new ArrayList();
  String name;

  public Menu(String name) {
    this.name = name;
  }

  public void add(MenuComponent menuComponent) {
    menuComponents.add(menuComponent);
  }

  public void remove(MenuComponent menuComponent) {
    menuComponents.remove(menuComponent);
  }

  public MenuComponent getChild(int i) {
    return (MenuComponent)menuComponents.get(i);
  }

  public String getName() { return name; }

  public void print() {
    System.out.print(", " + getName());

    Iterator iterator = menuComponent.iterator();
    while(iterator.hasNext()) {
      MenuComponent menuComponent = (MenuComponent)iterator.next();
      menuComponent.print();
    }
  }

  // 这个迭代器知道遍历任何组合
  public Iterator createIterator() {
    return new CompositeIterator(menuComponents.iterator());
  }
}

我们可为女招待加上一个可以确切告诉我们哪些项目是素食的方法

public class Waitress {
  MenuComponent allMenus;

  public Waitress(MenuComponent allMenus) {
    this.allMenus = allMenus;
  }

  public void printMenu() {
    allMenus.print();
  }

  public void printVegetarianMenu() {
    Iterator iterator = allMenus.createIterator();

    while(iterator.hasNext()) {
      MenuComponent menuComponent = (MenuComponent) iterator.next();
      menuComponent.print();
    }
  }
}

状态模式

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

首先定义各个状态

interface State {
  insertQuarter()
  ejectQuarter()
}

class SoldState implements State {
  // 初始化对象以便进行状态流转
  GumballMachine gumballMachine;

  public SoldState(GumballMachine gumballMachine) {
    this.gumballMachine = gumballMachine;
  }

  insertQuarter() {
    System.out.print("插入硬币");
    // 设置状态流转
    this.gumballMachine.setState(gumballMachine.getSoldOutState());
  }
  ejectQuarter() {
    System.out.print("拒绝硬币");
  }
}

class SoldOutState implements State {
  GumballMachine gumballMachine;

  public SoldState(GumballMachine gumballMachine) {
    this.gumballMachine = gumballMachine;
  }

  insertQuarter() {
    System.out.print("插入硬币");
    this.gumballMachine.setState(gumballMachine.getSoldState());
  }
  ejectQuarter() {
    System.out.print("拒绝硬币");
  }
}

之后将状态实例放在对应对象中

public class GumballMachine {
  State soldState;
  State soldOutState;

  State state = soldOutState;

  // 初始化各个状态实例
  public GumballMachine() {
    soldState = new SoldState(this);
    soldOutState = new SoldOutState(this);
  }

  public void insertQuarter() {
    state.insertQuarter();
  }

  public void ejectQuater() {
    state.ejectQuater();
  }

  // 设置状态
  void setState(State state) {
    this.state = state;
  }

  State getSoldState() {
    return soldState;
  }

  State getSoldOutState() {
    return soldOutState;
  }
}

代理模式

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

目前我们看看代理控制访问的方式

  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的资源
  • 保护代理基于权限控制对资源的访问

虚拟对象

虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象时才创建它。当对象在创建前和创建中时,由虚拟对象来扮演对象的替身,对象创建后,代理就会将请求直接委托给对象

比如在真正的图片加载出来之前我们提供占位图片

保护代理

保护代理就是一种根据访问权限决定客户可否访问对象的代理

Java 在 java.lang.reflect 包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类。因为实际的代理类是在运行时创建的,我们称这个 Java 技术为:动态代理

Java 已经为你创建了 Proxy 类,所以你需要有办法来告诉 Proxy 类你需要做什么。你不能像以前一样把代码放在 Proxy 类中,因为 Proxy 不是你直接实现的。你需要放在 InvocationHandler 中,而它的工作室响应代理的任何调用。

public interface PersonBean {
  String getName();
  int getHotOrNotRating();

  void setName(String name);
  void setHotOrNotRating(int rating);
}

public class PersonBeanImpl implements PersonBean {
  String name;
  int rating;
  int ratingCount = 0;

  public String getName() {
    return name;
  }

  public int getHotOrNotRating() {
    if (ratingCount == 0) return 0;
    return (rating / ratingCount)l;
  }

  public void setName(String name) {
    this.name = name
  }

  public void setHotOrNotRating(int rating) {
    this.rating += rating;
    ratingCount++;
  }
}

创建这种代理我们必须使用 Java API 的动态代理。Java 会为我们创建两个代理,我们只需要提供 handler 来处理代理转来的方法

// 步骤一 创建 InvocationHandler,我们需要写两个 InvocationHandler,其中一个给拥有者使用,另一个给非拥有者使用

import java.lang.reflect.*;

public class OwnerInvocationHandler implements InvocationHandler {
  PersonBean Person;
  // 将 person 传入构造器,并保持它的引用
  public OwnerInvocationHandler(PersonBean person) {
    this.person = person;
  }

  // 每次 proxy 的方法被调用,就会导致 proxy 调用此方法
  public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
    try {
      // 如果是 get 方法就调用 person 的方法
      if (method.getName().startsWith("get")) {
        return method.invoke(person, args);
      } else if (method.getName().equals("setHotOrNotRating")) {
        throw new IllegalAccessException();
      // 有 set 时,就可在真正主题上调用它
      } else if (method.getName().startsWith("set")) {
        return method.invoke(person, args);
      }
    } catch(InvocationTargetException e) {
      e.printStackTrance();
    }
    return null;
  }
}
// 需要一个 person 对象作为参数,并知道如何为 PersonBean 对象创建拥有者代理的方法。也就是说,我们要创建一个代理,并肩它的方法调用转发给 OwnerInvocationHandler

PersonBean getOwnerProxy(PersonBean person) {
  return (PersonBean) Proxy.newProxyInstance(
    person.getClass().getClassLoader(),
    person.getClass().getInterfaces(),
    new OwnerInvocationHandler(person));
  )
}

复合模式

复合模式就是在设计中携手合作征服许多问题的模式

模式通常被一起使用,并被组合在同一个设计解决方案中。复合模式在一个解决方案中结合两个或多个模式,以解决一般或重复发生的问题