常規設計模式
設計模式概述
創建型模式: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
為創建一組相關或相互依賴的對象(即產品族)提供一個借口,無需指定它們的具體類,我們使用了抽象工廠
舉個簡單的例子,生產電腦
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()方法
prototype
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)是用于把抽象化與實現化解耦,使得二者可以獨立變化
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)代理。
- 和適配器模式的區別:適配器模式主要改變所考慮對象的接口,而代理模式不能改變所代理類的接口。
和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制。
proxy
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都是最常見的。
使用場景:
- 如果在一個系統里面有許多類,它們之間的區別僅在于它們的行為,那么使用策略模式可以動態地讓一個對象在許多行為中選擇一種行為。
- 一個系統需要動態地在幾種算法中選擇一種。
如果一個對象有很多的行為,如果不用恰當的模式,這些行為就只好使用多重的條件選擇語句來實現。
strategy
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
請求以命令的形式包裹在對象中,并傳給調用對象。調用對象尋找可以處理該命令的合適的對象,并把該命令傳給相應的對象,該對象執行命令。
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)是用來降低多個對象和類之間的通信復雜性。這種模式提供了一個中介類,該類通常處理不同類之間的通信,并支持松耦合,使代碼易于維護。
使用場景:
- 系統中對象之間存在比較復雜的引用關系,導致它們之間的依賴關系結構混亂而且難以復用該對象。
想通過一個中間類來封裝多個類中的行為,而又不想生成太多的子類。
mediator
將兩個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、實現了信息的封裝,使得用戶不需要關心狀態的保存細節。
memento
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 解析、符號處理引擎等。
編譯器會用到,一般很少會用到
interpreter
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
優點:
- 封裝了轉換規則。
- 枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。
- 將所有與某個狀態有關的行為放到一個類中,并且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
- 允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
- 可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。
缺點:
- 狀態模式的使用必然會增加系統類和對象的個數。
- 狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂。
- 狀態模式對"開閉原則"的支持并不太好,對于可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的源代碼。
使用場景:
- 行為隨狀態改變而改變的場景。
條件、分支語句的代替者。
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)模式,也是一種責任鏈模式。只不過有一些細微的差別。
responsibility
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
訪問者模式的目的是封裝一些施加于某種數據結構元素之上的操作。一旦這些操作需要修改的話,接受這個操作的數據結構則可以保持不變。
優點:
- 符合單一職責原則。
- 優秀的擴展性。
- 靈活性。
缺點:
- 具體元素對訪問者公布細節,違反了迪米特原則。
- 具體元素變更比較困難。
- 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。
使用場景:
- 對象結構中對象對應的類很少改變,但經常需要在此對象結構上定義新的操作。
-
需要對一個對象結構中的對象進行很多不同的并且不相關的操作,而需要避免讓這些操作"污染"這些對象的類,也不希望在增加新操作時修改這些類。
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種常規設計模式外,還衍生出了一些其它的設計模式。后續文章中會慢慢講到