常规设计模式
设计模式概述
创建型模式:5
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:7
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:11
模板方法模式、策略模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、职责链模式、访问者模式
设计模式的六大原则
- 开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
- 里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
- 依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
- 接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
- 迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
- 合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
创建型模式
1 单例模式 Singleton
工作中最常用的设计模式之一,不多讲。懒汉恶汉等等,网上讲单例模式的已经很多了。其实我们只需要明白使用单例模式的目的以及常见的坑就足够了。
Effective Java作者推荐使用Enum,本人更喜欢内部类。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
public class Singleton2 {
private volatile static Singleton2 uniqueSingleton2;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (uniqueSingleton2 == null) {
synchronized (Singleton2.class) {
if (uniqueSingleton2 == null) {
uniqueSingleton2 = new Singleton2();
}
}
}
return uniqueSingleton2;
}
}
public class Singleton3 {
private static Singleton3 uniqueSingleton3 = new Singleton3();
private Singleton3() {
}
public static Singleton3 getInstance() {
return uniqueSingleton3;
}
}
public class Singleton4 {
private Singleton4() {
}
private static class SingletonFactory{
private static final Singleton4 INSTANCE = new Singleton4();
}
public static Singleton4 getInstance() {
return SingletonFactory.INSTANCE;
}
}
2 工厂模式 Factory Method
简单工厂模式 Simple Factory
1 首先要有一个产品接口
public interface IProduct {
void method01();
void method02();
}
2 然后要有具体的产品
public class ConcreteProductA implements IProduct {
public void method01() {
System.out.println("ConcreteProductA method01() ...");
}
public void method02() {
System.out.println("ConcreteProductA method02() ...");
}
}
public class ConcreteProductB implements IProduct{
public void method01() {
System.out.println("ConcreteProductB method01() ...");
}
public void method02() {
System.out.println("ConcreteProductB method02() ...");
}
}
3 简单工厂
一般都是根据传参来进行实例化,也可以使用命名不同的静态方法,还可以使用反射
public class Factory {
/**
* 反射
* @param clazz
* @param <T>
* @return
*/
public <T extends IProduct> T createProduct(Class<T> clazz){
T product = null;
try {
product = (T) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return product;
}
/**
* 使用不同命名的静态方法
* @return
*/
public static IProduct createProductA(){
return new ConcreteProductA();
}
public static IProduct createProductB(){
return new ConcreteProductB();
}
/**
* 根据穿参进行实例化
* @param name
* @return
*/
public static IProduct createProduct(String name){
IProduct product = null;
switch (name){
case "productA":
product = new ConcreteProductA();
break;
case "productB":
product = new ConcreteProductB();
break;
default:product = null;
}
return product;
}
}
工厂方法 Factory Method
1 和简单工厂一样,我们需要产品接口和具体的产品
2 不同的产品对应不同的工厂,所以我们需要抽象的工厂和具体的工厂(个人觉得和使用反射的简单工厂相比,仅仅只是减少了反射带来的风险。当然,另一个好处是一个具体的工厂可以有不同的创建方法)
abstract class AbstractFactory {
public abstract IProduct createProduct();
}
public class FactoryA extends AbstractFactory{
@Override
public IProduct createProduct() {
return new ConcreteProductA();
}
}
public class FactoryB extends AbstractFactory {
@Override
public IProduct createProduct() {
return new ConcreteProductB();
}
}
3 抽象工厂 Abstract Factory
为创建一组相关或相互依赖的对象(即产品族)提供一个借口,无需指定它们的具体类,我们使用了抽象工厂
举个简单的例子,生产电脑
1 假设我们需要不同型号的CPU和主板。我们需要CPU接口-具体实现,主板接口-具体实现
public interface Cpu {
void calculate();
}
public class IntelCpu implements Cpu{
private int pins = 0;
public IntelCpu(int pins) {
this.pins = pins;
}
@Override
public void calculate() {
System.out.println("Intel CPU的针脚数:" + pins);
}
}
public class AmdCpu implements Cpu {
private int pins = 0;
public AmdCpu(int pins) {
this.pins = pins;
}
@Override
public void calculate() {
System.out.println("AMD CPU的针脚数:" + pins);
}
}
public interface Mainboard {
void installCPU();
}
public class IntelMainboard implements Mainboard {
private int cpuHoles = 0;
public IntelMainboard(int cpuHoles) {
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU() {
System.out.println("Intel主板的CPU插槽孔数是:" + cpuHoles);
}
}
public class AmdMainboard implements Mainboard {
private int cpuHoles = 0;
public AmdMainboard(int cpuHoles) {
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU() {
System.out.println("AMD主板的CPU插槽孔数是:" + cpuHoles);
}
}
2 根据不同的配置我们生产不同的电脑,通过不同产品的工厂来生产不同的类型产品
public interface ComputerFactory {
Cpu createCpu(int pins);
Mainboard createMainBoard(int cpuHoles);
}
public class InterFactory implements ComputerFactory {
@Override
public Cpu createCpu(int pins) {
return new IntelCpu(pins);
}
@Override
public Mainboard createMainBoard(int cpuHoles) {
return new IntelMainboard(cpuHoles);
}
}
public class AmdFactory implements ComputerFactory {
@Override
public Cpu createCpu(int pins) {
return new AmdCpu(pins);
}
@Override
public Mainboard createMainBoard(int cpuHoles) {
return new AmdMainboard(cpuHoles);
}
}
4 建造者模式 Builder
这是一个大家经常会遇到的设计模式。Effective Java第二条讲的就是Builder模式的使用。
经典的Builder模式需要有以下部分:
- Product 产品抽象类。
- Builder 抽象的Builder类。
- ConcretBuilder 具体的Builder类。
- Director 同一组装过程。
下面我们看看谷歌大神在Guava里面是怎样使用Builder模式的(选择部分代码)
这里的内部类public static class Builder<K, V>即具体的Builder类,ImmutableMap<K, V>即产品类,通过ImmutableMap的builder方法产生Builder,又通过Builder的build(类似Director的组装方法)方法构造ImmutableMap
public abstract class ImmutableMap<K, V> implements Map<K, V>, Serializable {
public static <K, V> ImmutableMap<K, V> of() {
return ImmutableBiMap.of();
}
public static <K, V> ImmutableMap<K, V> of(K k1, V v1) {
return ImmutableBiMap.of(k1, v1);
}
public static <K, V> Builder<K, V> builder() {
return new Builder<K, V>();
}
public static class Builder<K, V> {
Comparator<? super V> valueComparator;
ImmutableMapEntry<K, V>[] entries;
int size;
boolean entriesUsed;
public Builder() {
this(ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY);
}
@SuppressWarnings("unchecked")
Builder(int initialCapacity) {
this.entries = new ImmutableMapEntry[initialCapacity];
this.size = 0;
this.entriesUsed = false;
}
public Builder<K, V> put(K key, V value) {
ensureCapacity(size + 1);
ImmutableMapEntry<K, V> entry = entryOf(key, value);
// don't inline this: we want to fail atomically if key or value is null
entries[size++] = entry;
return this;
}
public Builder<K, V> put(Entry<? extends K, ? extends V> entry) {
return put(entry.getKey(), entry.getValue());
}
/**
* Associates all of the given map's keys and values in the built map.
* Duplicate keys are not allowed, and will cause {@link #build} to fail.
*
* @throws NullPointerException if any key or value in {@code map} is null
*/
public Builder<K, V> putAll(Map<? extends K, ? extends V> map) {
return putAll(map.entrySet());
}
public ImmutableMap<K, V> build() {
switch (size) {
case 0:
return of();
case 1:
return of(entries[0].getKey(), entries[0].getValue());
default:
/*
* If entries is full, then this implementation may end up using the entries array
* directly and writing over the entry objects with non-terminal entries, but this is
* safe; if this Builder is used further, it will grow the entries array (so it can't
* affect the original array), and future build() calls will always copy any entry
* objects that cannot be safely reused.
*/
if (valueComparator != null) {
if (entriesUsed) {
entries = ObjectArrays.arraysCopyOf(entries, size);
}
Arrays.sort(
entries,
0,
size,
Ordering.from(valueComparator).onResultOf(Maps.<V>valueFunction()));
}
entriesUsed = size == entries.length;
return RegularImmutableMap.fromEntryArray(size, entries);
}
}
}
}
看完大神的代码,尝试一下自己的实现
public class User {
private final String firstName; // 必传参数
private final String lastName; // 必传参数
private final int age; // 可选参数
private final String phone; // 可选参数
private final String address; // 可选参数
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
private String address;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
5 原型模式 Prototype
原型模式主要是为了实现浅复制。在Java中只需要实现Closeable接口,重写Object的clone()方法
public abstract class Shape implements Cloneable{
private String id;
protected String type;
public abstract void draw();
public String getType(){
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
public class Rectangle extends Shape {
public Rectangle(){
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Circle extends Shape {
public Circle(){
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public class Square extends Shape {
public Square(){
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
public synchronized static void setPrototype(String shapeId , Shape shape){
shapeMap.put(shapeId, shape);
}
}
public class Client {
public static void main(String[] args) {
Circle circle = new Circle();
circle.setId("1");
ShapeCache.setPrototype(circle.getId(),circle);
Square square = new Square();
square.setId("2");
ShapeCache.setPrototype(square.getId(),square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
ShapeCache.setPrototype(rectangle.getId(),rectangle);
System.out.println("Shape : " + ShapeCache.getShape("1").getType());
System.out.println("Shape : " + ShapeCache.getShape("2").getType());
System.out.println("Shape : " + ShapeCache.getShape("3").getType());
}
}
结构型模式
6 适配器模式 Adapter
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。
我想说,这应该是最常见的设计模式了。但是建议尽量少用,使用不当会使系统变得特别复杂
适配器模式的实现一般有两种,继承和组合。推荐使用组合的方式
public class Adaptee {
public void sampleOperation1(){
System.out.println("adaptee");
}
}
public interface Target {
/**
* 这是源类Adaptee也有的方法
*/
void sampleOperation1();
/**
* 这是源类Adapteee没有的方法
*/
void sampleOperation2();
}
public class Adapter implements Target{
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
/**
* 源类Adaptee有方法sampleOperation1
* 因此适配器类直接委派即可
*/
public void sampleOperation1(){
this.adaptee.sampleOperation1();
}
/**
* 源类Adaptee没有方法sampleOperation2
* 因此由适配器类需要补充此方法
*/
public void sampleOperation2(){
//写相关的代码
System.out.print("adapter");
}
}
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.sampleOperation1();
target.sampleOperation2();
}
}
7 桥接模式 Bridge
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化
public interface DrawAPI {
void drawCircle(int radius, int x, int y);
}
public class GreenCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
public class RedCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
public abstract class Shape {
protected DrawAPI drawAPI;
public Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
public class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(100,100, 10, new RedCircle());
Shape greenCircle = new Circle(100,100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();
}
}
8 装饰模式 Decorator
- 需要扩展一个类的功能。
- 动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删)
装饰器不想讲。大名鼎鼎的I/O字符字节流源码,大家自己看API和源代码。
个人觉得字节字符流的很多类是没有必要的或者可以大量合并的。为了兼容以前的接口或者需要增加某些小功能,而增加了过多的装饰类,让Java的I/O模块看起来十分臃肿。(哈哈,一不小心批评了大神的API)
9 组合模式 Composite
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次
简单来说,就是类拥有自己的实例
下面是一个BinarySearchTree的例子,每个节点分别拥有自己的左右子节点
public class BinarySearchTree<T extends Comparable<? super T>> {
private BinaryNode<T> root; //root节点
public BinarySearchTree() {
this.root = null;
}
public void makeEmpty() {
root = null;
}
public boolean isEmpty() {
return root == null;
}
public boolean contain(T x) {
return contain(x, root);
}
public T findMin() {
if (isEmpty()) throw new IllegalArgumentException();
return findMin(root).element;
}
public T findMax() {
if (isEmpty()) throw new IllegalArgumentException();
return findMax(root).element;
}
public void insert(T x) {
root = insert(x, root);
}
public void remove(T x) {
root = remove(x, root);
}
/**
* Internal method to find an item in a subtree
*
* @param x is item to search for
* @param t is the node that roots the subtree
* @return node containing the mached item
*/
private boolean contain(T x, BinaryNode<T> t) {
if (t == null) {
return false;
}
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
return contain(x, t.left);
} else if (compareResult > 0) {
return contain(x, t.right);
} else {
return true;
}
}
/**
* Internal method to find the smallest item in the subtree
*
* @param t the node that roots the subtree
* @return the smallest item
*/
private BinaryNode<T> findMin(BinaryNode<T> t) {
if (t == null) {
return null;
} else if (t.left == null) {
return t;
} else {
return findMin(t.left);
}
}
/**
* Internal method to find the largest item in the subtree
*
* @param t the node that roots the subtree
* @return the largest item
*/
private BinaryNode<T> findMax(BinaryNode<T> t) {
if (t != null) {
while (t.right != null) {
t = t.right;
}
}
return t;
}
/**
* Internal method to insert into the subtree
*
* @param x the item to insert
* @param t the node that roots the subtree
* @return the new root of the subtree
*/
private BinaryNode<T> insert(T x, BinaryNode<T> t) {
if (t == null) {
return new BinaryNode<T>(x, null, null);
}
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
t.left = insert(x, t.left);
} else if (compareResult > 0) {
t.right = insert(x, t.right);
}
return t;
}
/**
* Internal method to remove from a subtree
*
* @param x the item to remove
* @param t the node that roots the subtree
* @return the new root of the subtree
*/
private BinaryNode<T> remove(T x, BinaryNode<T> t) {
if (t == null) {
return t;
}
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
t.left = remove(x, t.left);
} else if (compareResult > 0) {
t.right = remove(x, t.right);
} else if (t.left != null && t.right != null) {
t.element = findMin(t.right).element;
t.right = remove(t.element, t.right);
} else {
t = t.left != null ? t.left : t.right;
}
return t;
}
/**
* 查找二叉树节点类
*
* @param <T>
*/
private static class BinaryNode<T> {
private T element;
private BinaryNode<T> left;
private BinaryNode<T> right;
public BinaryNode(T element) {
this(element, null, null);
}
public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) {
this.element = element;
this.left = left;
this.right = right;
}
}
}
10 外观模式 Facade
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用
使用外观模式的目的就是让我们使用更加简单(脑残)。记得我在一篇讲Netty编解码的时候讲到过Facebook提供的构建在Netty与Thrift之上的封装nifty: https://github.com/facebook/nifty。我确信无疑,里面使用了外观模式。(外观模式,也不想讲。当你遇到某些客户时,你会考虑使用它的)
11 享元模式 Flyweight
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,实现对象的共享,以减少内存占用和提高性能。
享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。
常见的数据库连接池,String都使用了享元模式。
第一次听说享元模式时,别人举得是围棋的例子。从色彩上来看,围棋只有两种颜色,而围棋的落子却有不同的组合。所以我们将公有属性定义为Key,放在一个HashMap里面。
public class Circle{
private String color;
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
public void draw() {
System.out.println("Circle: Draw() [Color : " + color
+ ", x : " + x + ", y :" + y + ", radius :" + radius);
}
}
public class ShapeFactory {
private static final HashMap<String, Circle> circleMap = new HashMap();
public static Circle getCircle(String color) {
Circle circle = circleMap.get(color);
if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
public class FlyweightPatternDemo {
private static final String colors[] =
{"Red", "Green", "Blue", "White", "Black"};
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Circle circle = ShapeFactory.getCircle(getRandomColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int) (Math.random() * colors.length)];
}
private static int getRandomX() {
return (int) (Math.random() * 100);
}
private static int getRandomY() {
return (int) (Math.random() * 100);
}
}
12 代理模式 Proxy
按职责来划分,通常有以下使用场景:
1、远程代理。
2、虚拟代理。
3、Copy-on-Write 代理。
4、保护(Protect or Access)代理。
5、Cache代理。
6、防火墙(Firewall)代理。
7、同步化(Synchronization)代理。
8、智能引用(Smart Reference)代理。
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
public class ProxyTest {
public static void main(String[] args) {
Sourceable source = new ProxyObject();
source.method();
ProxyHandler handler = new ProxyHandler(source);
Sourceable sourceable = (Sourceable) Proxy.newProxyInstance(ProxyObject.class.getClassLoader(),source.getClass().getInterfaces(),handler);
sourceable.method();
}
}
静态代理
public interface Sourceable {
void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
public class ProxyObject implements Sourceable {
private Source source;
public ProxyObject(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method();
after();
}
private void after() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
动态代理
public class ProxyHandler implements InvocationHandler {
private Sourceable sourceable;
public ProxyHandler(Sourceable subject){
this.sourceable = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(sourceable, args);
after();
return null;
}
private void after() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
行为型模式
13 模板方法模式 Template Method
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
我的理解就是——钩子
模板方法,懒得讲了
public abstract class Game {
public void initialize(){
System.out.println("Game Initialized! Start playing.");
}
/**
* 钩子方法
*/
abstract void startPlay();
public void endPlay(){
System.out.println("Game Finished!");
}
//模板
public final void play(){
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
14 策略模式 Strategy
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
一看到context,一种熟悉感是不是油然而生。不管是在Spring中,还是在Spark的使用中,context都是最常见的。
使用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
public interface Strategy {
int doOperation(int num1, int num2);
}
public class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubstract implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class OperationMultiply implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
15 命令模式 Command
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
public interface Command {
void execute();
}
public class AddCommand implements Command {
private Document document;
public AddCommand(Document document) {
this.document = document;
}
@Override
public void execute() {
this.document.add();
}
}
public class RedoCommand implements Command {
private Document document;
public RedoCommand(Document document) {
this.document = document;
}
@Override
public void execute() {
this.document.redo();
}
}
public class UndoCommand implements Command {
private Document document;
public UndoCommand(Document document) {
this.document = document;
}
@Override
public void execute() {
this.document.undo();
}
}
public class Document {
public static StringBuffer sbr = new StringBuffer();
/**
* 计数器
*/
public static int count = 0;
/**
* 撤销实现方法
*/
public void undo() {
System.out.println("调用撤销实现方法,字符串递减");
sbr.deleteCharAt(sbr.length() - 1);
count--;
System.out.println("当前文本为:" + sbr.toString());
}
/**
* 恢复实现方法
*/
public void redo() {
System.out.println("调用恢复实现方法,字符串递加");
this.sbr.append(count);
count++;
System.out.println("当前文本为:" + sbr.toString());
}
/**
* 执行实现方法
*/
public void add() {
System.out.println("调用执行实现方法,字符串递加");
this.sbr.append(count);
count++;
System.out.println("当前文本为:" + sbr.toString());
}
}
public class Invoker {
private Command command;
public void setCommand(Command cmd){
this.command = cmd;
}
public void execute() {
this.command.execute();
}
}
public class Client {
public static void main(String args[]){
Document doc = new Document(); //文档实体对象
AddCommand addCmd = new AddCommand(doc); //具体命令实体对象
UndoCommand undoCmd = new UndoCommand(doc); //具体命令实体对象
RedoCommand redoCmd = new RedoCommand(doc); //具体命令实体对象
Invoker invoker = new Invoker(); //调用者对象
invoker.setCommand(addCmd);
invoker.execute();
invoker.setCommand(addCmd);
invoker.execute();
invoker.setCommand(undoCmd);
invoker.execute();
invoker.setCommand(redoCmd);
invoker.execute();
}
}
其实命令模式和策略模式的结构超级像。只不过Command将不同的行为封装在一个接收者中,而Strategy是将不同的行为或者算法封装在不同的Strategy中。
16 迭代器模式 Iterator (略过)##
17 观察者模式 Observer
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
Java API提供了观察者接口Observer和被观察者类Observable。
Zookeeper的Watcher实现也是观察者模式的实用。
我们尝试写一个比较通用的监听者模式
public interface Subject {
void addListener(Listener listener);
void removeListener(Listener listener);
void notifyListeners();
void setChanged();
void doSomeThing();
}
public interface Listener<T extends Subject> {
void update(T t);
}
public abstract class AbstractSubject implements Subject {
private final List<Listener> listeners;
private AtomicBoolean changed = new AtomicBoolean(false);
public AbstractSubject() {
this.listeners = new ArrayList<>();
}
@Override
public void addListener(Listener listener) {
synchronized (listeners){
if(!listeners.contains(listener)) listeners.add(listener);
}
}
@Override
public void removeListener(Listener listener) {
synchronized (listeners){
listeners.remove(listener);
}
}
@Override
public void notifyListeners() {
List<Listener> local;
synchronized (listeners){
local = Collections.unmodifiableList(listeners);
}
for(Listener listener:local){
listener.update(this);
}
}
@Override
public void setChanged() {
changed.compareAndSet(false, true);
notifyListeners();
changed.compareAndSet(true, false);
}
public abstract void doSomeThing();
}
18 中介者模式 Mediator
中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。
使用场景:
- 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
将两个User类看做租房者和中介者,一个work()是交钱,一个work()是给钥匙。
public interface Mediator {
void createMediator();
void workAll();
}
public abstract class User {
private Mediator mediator;
public User(Mediator mediator) {
this.mediator = mediator;
}
public Mediator getMediator(){
return mediator;
}
public abstract void work();
}
public class User1 extends User {
public User1(Mediator mediator) {
super(mediator);
}
@Override
public void work() {
System.out.println("user1 exe!");
}
}
public class User2 extends User {
public User2(Mediator mediator) {
super(mediator);
}
@Override
public void work() {
System.out.println("user2 exe!");
}
}
public class MyMediator implements Mediator {
private User user1;
private User user2;
public User getUser1() {
return user1;
}
public User getUser2() {
return user2;
}
@Override
public void createMediator() {
user1 = new User1(this);
user2 = new User2(this);
}
@Override
public void workAll() {
user1.work();
user2.work();
}
}
public class Client {
public static void main(String[] args) {
Mediator mediator = new MyMediator();
mediator.createMediator();
mediator.workAll();
}
}
19 备忘录模式 Memento
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。
1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
2、实现了信息的封装,使得用户不需要关心状态的保存细节。
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState(){
return state;
}
}
public class Originator {
private String state;
public void setState(String state){
this.state = state;
}
public String getState(){
return state;
}
public Memento saveStateToMemento(){
return new Memento(state);
}
public void getStateFromMemento(Memento Memento){
state = Memento.getState();
}
}
public class CareTaker {
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento state){
mementoList.add(state);
}
public Memento get(int index){
return mementoList.get(index);
}
}
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #3");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
20 解释器模式 Interpreter
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
编译器会用到,一般很少会用到
public class Context {
private int num1;
private int num2;
public Context(int num1, int num2) {
this.num1 = num1;
this.num2 = num2;
}
public int getNum1() {
return num1;
}
public void setNum1(int num1) {
this.num1 = num1;
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
public interface Expression {
int interpret(Context context);
}
public class Plus implements Expression {
@Override
public int interpret(Context context) {
return context.getNum1()+context.getNum2();
}
}
public class Minus implements Expression {
@Override
public int interpret(Context context) {
return context.getNum1()-context.getNum2();
}
}
public class Client {
public static void main(String[] args) {
// 计算9+2-8的值
int result = new Minus().interpret((new Context(new Plus()
.interpret(new Context(9, 2)), 8)));
System.out.println(result);
}
}
21 状态模式 State
优点:
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景:
- 行为随状态改变而改变的场景。
条件、分支语句的代替者。
public interface State {
void doAction(Context context);
}
public class StartState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
public class StopState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
22 责任链模式 Chain of Responsibility
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
其实,我们经常使用的过滤器(Filter)模式,也是一种责任链模式。只不过有一些细微的差别。
public abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
//责任链中的下一个元素
protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message){
if(this.level <= level){
write(message);
}
if(nextLogger !=null){
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
public class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
public class ErrorLogger extends AbstractLogger {
public ErrorLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
public class FileLogger extends AbstractLogger {
public FileLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}
public class ChainPatternDemo {
private static AbstractLogger getChainOfLoggers(){
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO,
"This is an information.");
loggerChain.logMessage(AbstractLogger.DEBUG,
"This is an debug level information.");
loggerChain.logMessage(AbstractLogger.ERROR,
"This is an error information.");
}
}
23 访问者模式 Visitor
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
优点:
- 符合单一职责原则。
- 优秀的扩展性。
- 灵活性。
缺点:
- 具体元素对访问者公布细节,违反了迪米特原则。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
使用场景:
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
-
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
public interface Visitable {
void accept(Visitor v);
}
public class ConcreteElementA implements Visitable {
@Override
public void accept(Visitor v) {
v.visit(this);
}
public void operate() {
System.out.println("ConcreteElementA ....");
}
}
public class ConcreteElementB implements Visitable {
@Override
public void accept(Visitor v) {
v.visit(this);
}
public void operate() {
System.out.println("ConcreteElementB ....");
}
}
public interface Visitor {
void visit(ConcreteElementB able);
void visit(ConcreteElementA able);
}
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementB able) {
able.operate();
}
@Override
public void visit(ConcreteElementA able) {
able.operate();
}
}
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementB able) {
}
@Override
public void visit(ConcreteElementA able) {
}
}
public class Client {
public static void main(String[] args) {
Visitor v1 = new ConcreteVisitorA();
List<Visitable> list = new ArrayList<>();
list.add(new ConcreteElementA());
list.add(new ConcreteElementB());
for(Visitable able :list){
able.accept(v1);
}
}
}
后记
其实在工作中,我们会使用到很多设计模式。可能有时候你并不知道它是一种设计模式,只是你觉得这样使用比较方便。所以,系统的学习并尝试去使用设计模式的组合,养成一种良好的设计习惯,是很有必要的。
除了上述23种常规设计模式外,还衍生出了一些其它的设计模式。后续文章中会慢慢讲到