使用设计模式可以有效的提高开发效率和软件的健壮性等特性。设计模式可以分为下面几类:
结构型模式行为类模式创建型模式设计模式有很多种,本文会给出一些有代表性的设计模式。
结构型模式:
Adapter 适配器模式Decorator 装饰器模式Façade 外观模式Proxy 代理模式行为类模式:
Strategy 策略模式Template Method 模板模式Iterator 迭代器模式Observer 观察者模式Visitor 访问者模式创建型模式:
Factory method 工厂方法模式Abstract factory 抽象工厂模式在给出设计模式之前,先给出委托的概念。
委托是一项提高软件复用性的技术,相较于继承机制,委托更加灵活多变,能够有效的降低耦合度。很多设计模式都是讲继承和委托结合使用。委托发生在对象层面,而继承发生在类层面。
总的思路是:把A的功能委派给B来实现。
结构框图:
例子:
由于动物有多个行为属性,而每个属性又有多个取值,这个需求如果使用继承实现,会导致大量的组合。
下面给出使用委托机制的实现
// 定义两个抽象的行为接口 interface Flyable{ public void fly(); } interface Quackable{ public void quack(); } //再给出两个接口的实现,每个接口可以有多个实现,但这里每个接口只给出一个实现 class FlyWithWings implements Flyable{ @override public void fly(){ System.out.println("fly with wings"); } } class Quack implements Quackable{ @override public void fly(){ System.out.println("quack like duck"); } }上面的这些代码实际上是设计了被委托对象(也就是委托中的B)。下面再给出A如何进行委托。
interface Ducklike extends Flyable,Quackable{ //这里通过接口的组合,定义了行为的组合 } public class Duck implements Ducklike{//这里的Duck类通过继承Ducklike接口,拥有了相应的行为 //这里是在设置委托。这里直接进行了绑定,是委托中的组合(Composition),还有其他的几种委托,将在下面给出。 Flyable flyBehavior = new FlyWithWings(); Quackable quackBehavior = new Quack(); //这里通过委托来实现具体的行为 @override public void fly(){ this.flyBehavior.fly() } @override public void quack(){ this.quackBehavior.fly() } }接下来给出客户端使用方法。
//客户端的使用很简单 Duck d = new Duck(); d.fly(); d.quack();委托根据具体的实现还可以分为几类。
依赖(Dependency):A use B关联(Association):A has B组合(Composition)/聚合(aggregation):A owns B4其中,组合和聚合可以视作关联的两种具体形态。
下面解释这几个类型的区别。
通过方法的参数或者在方法的局部中使用时发生联系。
class Duck{ //这里不再保存Flyable对象 void fly(Flyable f){ //通过外部传入参数来进行委托 f.fly(); } } //外部调用的时候,需要传入一个参数 Flyable f = new FlyWithWings(); Duck d = new Duck(); d.fly(f);从上面的代码可以看出,Duck类中不再保存对应的被委托者(B)的对象,转而使用传入参数来进行委托。所以这个委托只是临时的。
与上面的临时性委托相反,这里A会保存B的对象,从而达到建立永久性的联系。
Association可以分为Composition和Aggregation,两者的区别是,前者保存被委托者的对象时直接绑定了具体的对象,而后者仅仅是声明,未进行绑定。
Composition:
class Duck{ //这里直接进行了绑定 Flyable f = new FlyWithWings(); void fly(){ f.fly(); } }Aggregation:
class Duck{ //这里仅仅声明了变量 Flyable f; //这里根据外界传入的参数来进行绑定 void Duck(Flyable f){ this.f = f; } void fly(){ f.fly(); } }这个模式用来解决接口不兼容的问题。通过增加一个接口,将已存在的子类封装起来,然后client面向这个新增的接口进行编程,这样既解决了不兼容问题,也隐藏了具体子类。
结构框图:
适配器模式可以通过继承和委托两种方式实现。
例子:
一个LegacyRectangle类有一个display()方法,接收参数是左上角坐标+宽+高。但是客户端想接收左上角坐标+右下角坐标。这样就造成了不兼容的问题。我们使用适配器模式来解决这个问题。
设计图:
代码:
interface Shape{ //client期望的参数列表形式 void dispaly(int x1, int y1, int x2, int y2); } class LegacyRectangle{ //我们实际的参数列表形式,和上面的不兼容 void dispaly(int x1, int y1, int w, int h){ ... } } class Rectangle implements Shape{//Adaptor类实现抽象接口 void display(int x1, int y1, int x2, int y2){ new LegacyRectangle().display(x1, y1, x2-x1, y2-y1); } } class Client { //客户端可以直接对抽象接口进行编程 Shape shape = new Rectangle(); public display() { shape.display(x1, y1, x2, y2); } }这个模式主要用来解决子类特征组合的问题。对每一个特征构造子类,通过委托机制增加到对象上。
设计框图:
其中Decorator抽象类是所有装饰类的基类,里面包含的成员变量component指向了被装饰的对象。
例子:
对于一个最基本的Stack类,实现具有多种功能的子类。如SecureStack、UndoStack、SynchronizedStack。
设计图:
代码:
下面的代码是实现一个最基本的Stack类,当作被装饰物。
interface Stack { void push(Item e); Item pop(); } public class ArrayStack implements Stack { //实现一个最基本的Stack类,当作被装饰物 ... //rep public ArrayStack() {...} public void push(Item e) { ... } public Item pop() { ... } ... }再写Decorator抽象类
//给出一个用于 decorator的基础类 public abstract class StackDecorator implements Stack { protected final Stack stack; //这个成员变量用来保存被装饰对象 public StackDecorator(Stack stack) { this.stack = stack; } //下面的Stack基础功能仍然是通过委托实现 public void push(Item e) { stack.push(e); } public Item pop() { return stack.pop(); } ... }下面给出具体的装饰,只给出一个,其他的特征写法类似
public class UndoStack extends StackDecorator { private final UndoLog log = new UndoLog(); public UndoStack(Stack stack) { super(stack); } public void push(Item e) { super.push(e); //基础功能通过委托实现 log.append(UndoLog.PUSH, e);//增加新特性 } public void undo() {//增加新特性 //实现修饰的功能 ... } }再给出使用方法
Stack s = new ArrayStack();//创建基础类 Stack t = new UndoStack(new ArrayStack());//创建一个具undo特征的stack Stack t = new SecureStack(new SynchronizedStack( new UndoStack(s)))//创建一个多特征的StackFaçade模式很简单,目的是提供一个统一的接口来取代一系列小接口调用,相当于对复杂 系统做了一个封装,简化客户端使用。
设计框图:
这个模式很简单就不写了。
某个对象比较“敏感”“私密”/“贵重”,不希望被 client直接访问到,故设置 proxy,在二者之间建立防火墙。这个模式和适配器模式相似,但是针对的应用场景不同,适配器主要解决的是Client和ADT之间的接口不兼容问题,代理模式是为了隔离对复杂对象的访问,降低访问的代价。
设计框图:
例子:
从磁盘加载图片,有时候仅仅只是需要图片的对象,不需要真正的从磁盘加载图片,如果直接创建Image对象会产生很高的代价。
代码:
public interface Image{ void display(); } public class RealImage implements Image { private String fileName; public RealImage(String fileName){ this.fileName = fileName; loadFromDisk(fileName); } @Override public void display() {…} private void loadFromDisk(String fileName){…}//每次创建都要从磁盘加载,代价高 } public class ProxyImage implements Image { private Image realImage; private String fileName; public ProxyImage(String fileName){ this.fileName = fileName;//这里创建一个Image就不需要从磁盘加载 } @Override public void display() {//调用display,真正用到Image时候再开始加载 if(realImage == null){ realImage = new RealImage(fileName);//通过委托机制 } realImage.display(); } }这个模式也很简单,就是有多种不同的算法来实现同一个任务,但需要client根据需要 动态切换算法,而不是写死在代码里。为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。
设计框图:
这个很简单也不写了。
这个方法的思想是,给出做事情的步骤顺序,但是每一步有很多种实现方法。
设计框图:
其中TemplateAbstraction是一个抽象类,规定了方法有哪些步骤以及步骤的顺序。然后具体的步骤实现是在子类中。
例:
造一辆汽车的步骤顺序是一样的,但是每个步骤不同的汽车公司不一样。
设计如下:
代码:
抽象类:
public abstract class CarBuilder { protected abstract void BuildSkeleton(); protected abstract void InstallEngine(); protected abstract void InstallDoor(); public void BuildCar() { //造车通用逻辑 BuildSkeleton(); InstallEngine(); InstallDoor(); } }子类:
public class PorcheBuilder extends CarBuilder { protected void BuildSkeleton() { System.out.println("Building Porche Skeleton"); protected void InstallEngine() { System.out.println("Installing Porche Engine"); } protected void InstallDoor() { System.out.println("Installing Porche Door"); } } public class BeetleBuilder extends CarBuilder { protected void BuildSkeleton() { System.out.println("Building Beetle Skeleton"); } protected void InstallEngine() { System.out.println("Installing Beetle Engine"); } protected void InstallDoor() { System.out.println("Installing Beetle Door"); } }使用方法:
public static void main(String[] args) { CarBuilder c = new PorcheBuilder(); c.BuildCar(); c = new BeetleBuilder(); c.BuildCar(); }提供遍历容器中内容的方式。
设计框图:
先实现Iterable接口,再写一个具体的Iterator类
例子:
public class Pair<E> implements Iterable<E> { private final E first, second; public Pair(E f, E s) { first = f; second = s; } public Iterator<E> iterator() { return new PairIterator(); } //内部类 private class PairIterator implements Iterator<E> { private boolean seenFirst = false, seenSecond = false; public boolean hasNext() { return !seenSecond; } public E next() { if (!seenFirst) { seenFirst = true; return first; } if (!seenSecond) { seenSecond = true; return second; } throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } } }这是.一种“发布-订阅”形式,发布方的变化,会通知订阅方 。订阅方在发布方进行注册。这个模式可以类比粉丝和偶像,粉丝希望得知偶像的一举一动,于是粉丝就去偶像那里注册,偶像一旦有消息,就会推送给已经注册的粉丝(即回调粉丝的特定功能)。java提供了Observer接口,可以直接使用。
回调的目的大致可以分为:
推送通知推送数据拉取数据设计框图:
例子:
public class Subject {//这里是偶像 private List<Observer> observers = new ArrayList<Observer>();//维护粉丝列表 private int state;//偶像的状态 public int getState() {return state;}//供粉丝和外界获取状态 public void setState(int state) {//设置状态 this.state = state; notifyAllObservers();//状态更改之后,通知所有粉丝 } public void attach(Observer observer){observers.add(observer);}//添加一个粉丝 //这里也可以有删除粉丝的功能 private void notifyAllObservers(){ for (Observer observer : observers) { observer.update(); } } } public abstract class Observer { protected Subject subject;//这里保存了粉丝的偶像,这个属性可有可无,如果没有的话,粉丝就是完全被动的 public abstract void update();//供偶像回调的函数 } public class BinaryObserver extends Observer{//一个具体的粉丝 public BinaryObserver(Subject subject){ this.subject = subject;//指定自己的偶像 this.subject.attach(this); } @Override public void update() {//定制的行为 System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); } } public class ObserverPatternDemo {//客户端代码 public static void main(String[] args) { Subject subject = new Subject(); new HexaObserver(subject); new OctalObserver(subject); new BinaryObserver(subject); System.out.println("First state change: 15"); subject.setState(15); System.out.println("Second state change: 10"); subject.setState(10); } }这个模式为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下在需要时委托机制通过接入ADT。这个模式使用了双向委托。
设计框图:
例子
对不同商品进行结算,不同的商品,计算总价的方式不同。
代码:
/* Abstract element interface (visitable) */ public interface ItemElement {//这是一个visitable的接口 public int accept(ShoppingCartVisitor visitor); } /* Concrete element */ public class Book implements ItemElement{ private double price; ... int accept(ShoppingCartVisitor visitor) {//实现visit方法 visitor.visit(this);//把this传入 } } public class Fruit implements ItemElement{ private double weight; ... int accept(ShoppingCartVisitor visitor) {//实现visit方法 visitor.visit(this);//把this传入 } } /* Abstract visitor interface */ public interface ShoppingCartVisitor {//visitor接口 int visit(Book book); int visit(Fruit fruit); } public class ShoppingCartVisitorImpl implements ShoppingCartVisitor { public int visit(Book book) {//用来计算book的总价 int cost=0; if(book.getPrice() > 50){ cost = book.getPrice()-5; }else cost = book.getPrice(); System.out.println("Book ISBN::"+book.getIsbnNumber() + " cost ="+cost); return cost; } public int visit(Fruit fruit) {//用来计算fruit的总价 int cost = fruit.getPricePerKg()*fruit.getWeight(); System.out.println(fruit.getName() + " cost = "+cost); return cost; } }客户端代码:
public class ShoppingCartClient { public static void main(String[] args) { ItemElement[] items = new ItemElement[]{ new Book(20, "1234"),new Book(100, "5678"), new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")}; int total = calculatePrice(items); System.out.println("Total Cost = "+total); } private static int calculatePrice(ItemElement[] items) { ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl(); int sum=0; for(ItemElement item : items) sum = sum + item.accept(visitor); return sum; } }这个模式可以叫做虚拟构造器,使用户自定义的构造对象的方法。
结构框图:
这个模式很简单,就不举例了。要注意一点,最好把工厂类中的创建方法改成static,这样就不需要再多创建一个工厂对象。
这个模式相当于工厂方法模式的升级版本,工厂方法模式只能产生特定的某个产品,而抽象工厂模式可以产生一个由多个产品的组合体。实质两者是一致的。
例子:
从上面可以看出,一个抽象工厂实际上是多个工厂的特定组合。