设计模式原则:
单一职责原则:
就一个类而言,应该只有一个引起它变化的原因,即一个类应该只负责一项职责。如类A负责两个不同的职责:职责1、职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1、A2。
开放-封闭原则:
软件实体(类、模块、函数等)应该可以扩展但不能修改,即对扩展是开放的对修改是封闭的。
- 是编程中最基础。最重要的设计原则。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化。
- 编程中遵循其他原则以及使用是设计模式的目的就是遵循开闭原则。
依赖倒转原则:
- 高层模块不应该依赖低层模块,两个都应该依赖抽象。
- 抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程而不是针对实现编程。
优点:
- 依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构要比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类。
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
- 低层模板尽量都要有抽象类或接口,或者两个都有,程序稳定性更好。变量的声明类型尽量都是抽象类或接口,这样我们的变量变量引用和实际对象间就存在一个缓冲层,有利于程序的扩展和优化。
里氏替换原则:
子类必须能够替换掉它们的父类型。如果在程序中使用的是父类,那么换成它的子类同样可以,而且察觉不出父类和子类的区别。也就是说把父类都换成子类,程序的行为没有变化。
迪米特法则:
如果两个类不必直接通信,那么两个类就不应该发生直接的相互引用。如果其中一个类需要调用另外一个类的某一个方法的话,可以通过第三者转发这个调用。
在类的结构设计上,每一个类应尽量降低成员间的访问权限。每一个类应该包装好自己的private状态,不需要让别别的类知道的字段或行为就不要公开。
一、创建型模式:
特点说明:
创建型模式隐藏了类的实例是如何被创建和放在一起的,整个系统关于这些实例对象所知道的是由抽象类所定义的接口(符合依赖倒转原则)。
创建型模式抽象了实例化的过程,帮助一个系统独立于如何创建、表示和组合对象。通常设计应该是从工厂设计模式开始,当设计者需要更大的灵活性时,设计便会向其他创建型模式演化。
1.简单工厂模式
定义:简单工厂模式又叫静态工厂方法模式(Static FactoryMethod Pattern),是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
优点:对类的创建初始化全都交给一个工厂来执行,而用户不需要去关心创建的过程是什么样的,只用告诉工厂我想要什么就行了,对于客户端来说去除了与具体产品的依赖。
缺点:违背了设计模式的开闭原则,因为如果你要增加工厂可以初始化的类的时候,你必须对工厂进行改建。
例如以简单工厂模式实现一个运算器:
UML图:
代码:
//父类
public abstract class Operate {
double numberA;
double numberB;
public abstract double getResult();
}
public class Add extends Operate {
@Override
public double getResult() {
return numberA + numberB;
}
}
public class Sub extends Operate {
@Override
public double getResult() {
return numberA - numberB;
}
}
public class Mul extends Operate {
@Override
public double getResult() {
return numberA * numberB;
}
}
public class Div extends Operate {
@Override
public double getResult() {
if (numberB == 0) {
System.out.println("被除数不能为0");
return 0;
}
return numberA / numberB;
}
}
//工厂类
public class OperateFactory {
private static Operate operate;
public static Operate createOperate(String operateStr) {
switch (operateStr) {
case "+":
operate = new Add();
break;
case "-":
operate = new Sub();
break;
case "*":
operate = new Mul();
break;
case "/":
operate = new Div();
break;
default:
return operate;
}
return operate;
}
public static void main(String[] args) {
Operate operate = OperateFactory.createOperate("+");
operate.numberA = 1;
operate.numberB = 2;
System.out.println(operate.getResult());
}
}
2. 工厂模式
定义:定义一个创建对象的接口,让子类决定实例化哪一个类,工厂方法 使一个类的的实例化延迟到子类。工厂模式提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
特点:工厂模式把简单工厂的内部逻辑判断转移到客户端代码中进行。符合开放-封闭原则。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度
结构图:
代码:
public abstract class Operate {
double numberA;
double numberB;
public abstract double getResult();
}
public class AddOperate extends Operate {
@Override
public double getResult() {
return numberA + numberB;
}
}
public class MulOperate extends Operate {
@Override
public double getResult() {
return super.numberA * super.numberB;
}
}
public interface IFactory {
Operate getOperate();
}
public class AddFactory implements IFactory {
@Override
public Operate getOperate() {
// TODO Auto-generated method stub
return new AddOperate();
}
}
public class MulFactory implements IFactory {
@Override
public Operate getOperate() {
// TODO Auto-generated method stub
return new MulOperate();
}
}
public class Client {
public static void main(String[] args) {
int a = 2;
int b = 3;
IFactory factory = new MulFactory();
// IFactory factory = new AddFactory();
Operate add = factory.getOperate();
add.numberA = a;
add.numberB = b;
System.out.println(add.getResult());
}
}
3.抽象工厂模式
定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
特点:一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类只能创建一个具体产品类的实例。 抽象工厂模式: 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例。
优点:
1、最大的好处便是易于交换产品系列,由于具体工厂类,在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同产品配置。
2、它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
结构图:(从知乎复制,和代码不匹配)
代码:
//抽象工厂类
public interface PCFactory {
Mouse makeMouse();
KeyBoard makeKeyBoard();
}
//抽象产品类
public interface Mouse {
void clickMouse();
}
//抽象产品类
public interface KeyBoard {
void clickKeyBoard();
}
//具体产品类
public class HPMouse implements Mouse {
@Override
public void clickMouse() {
System.out.println("create hp mouse");
}
}
//具体产品类
public class HPKeyBoard implements KeyBoard {
@Override
public void clickKeyBoard() {
System.out.println("create hp keyboard");
}
}
//具体产品类
public class DellMouse implements Mouse {
@Override
public void clickMouse() {
System.out.println("create dell mouse");
}
}
//具体产品类
public class DellKeyBoard implements KeyBoard {
@Override
public void clickKeyBoard() {
System.out.println("create dell keyboard");
}
}
//具体工厂类
public class HpPCFactory implements PCFactory {
@Override
public Mouse makeMouse() {
return new HPMouse();
}
@Override
public KeyBoard makeKeyBoard() {
return new HPKeyBoard();
}
}
//具体工厂类
public class DellPCFactory implements PCFactory {
@Override
public Mouse makeMouse() {
return new DellMouse();
}
@Override
public KeyBoard makeKeyBoard() {
return new DellKeyBoard();
}
}
增加工厂:
假设我们增加华硕工厂,则我们需要增加华硕工厂,和戴尔工厂一样,继承PC厂商。之后创建华硕鼠标,继承鼠标类。创建华硕键盘,继承键盘类即可。
增加产品:
4.建造者模式
定义:建造者模式又被称呼为生成器模式,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。使用多个简单的对象一步一步构建成一个复杂的对象。它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
适用场景:需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品(增加具体Builder)。
结构图:
其中:
- 指挥者(HumanDirector):调用具体建造者来创建复杂对象(产品)的各个部分,并按照一定顺序(流程)来建造复杂对象。
- 抽象建造者(HumanBuilder):描述具体建造者的公共接口,一般用来定义建造细节的方法,并不涉及具体的对象部件的创建。
- 具体建造者(BasketBallPlayerBuilder):描述具体建造者,并实现抽象建造者公共接口。
- Human:描述一个由一系列部件组成较为复杂的对象。
代码:
public class HumanDirector {
public Human createHumanByDirector(HumanBuilder builder) {
builder.buildBody();
builder.buildFoot();
builder.buildHand();
builder.buildName();
builder.buildHead();
return builder.createHuman();
}
}
public interface HumanBuilder {
public void buildBody();
public void buildHand();
public void buildHead();
public void buildFoot();
public void buildName();
public Human createHuman();
}
public class BasketBallPlayerBuilder implements HumanBuilder {
private Human human;
public BasketBallPlayerBuilder() {
this.human = new Human();
}
@Override
public void buildBody() {
human.setBody("96 kg");
}
@Override
public void buildHand() {
human.setHand("long");
}
@Override
public void buildHead() {
human.setHead("tenacity");
}
@Override
public void buildFoot() {
human.setFoot("47");
}
@Override
public Human createHuman() {
return human;
}
@Override
public void buildName() {
human.setName("kobe");
}
}
//Product 具体产品类
public class Human {
private String name;
private String head;
private String hand;
private String foot;
private String body;
public void setHead(String head) {
this.head = head;
}
public void setHand(String hand) {
this.hand = hand;
}
public void setFoot(String foot) {
this.foot = foot;
}
public void setBody(String body) {
this.body = body;
}
public void setName(String name) {
this.name = name;
}
}
5.原型模式
定义:原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。说白了就是通过复制或者克隆更简单的获取相同或相似的对象实例。
结构图:
代码:
public interface Prototype {
public Object getCloneObj() throws CloneNotSupportedException;
}
public class ConcretePrototype1 implements Prototype, Cloneable {
@Override
public Object getCloneObj() throws CloneNotSupportedException {
return this.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Prototype p1 = new ConcretePrototype1();
Prototype p2 = (Prototype) p1.getCloneObj();
}
}
浅复制和深复制:详见
- 浅复制:
只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。 - 深复制:
除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。
深度克隆要深入到多少层,是一个不易确定的问题。在决定以深度克隆的方式复制一个对象的时候,必须决定对间接复制的对象时采取浅度克隆还是继续采用深度克隆。因此,在采取深度克隆时,需要决定多深才算深。此外,在深度克隆的过程中,很可能会出现循环引用的问题,必须小心处理。
二、结构型模式
1.适配器模式
定义:适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。
结构图:
代码:(美国电器 110V,中国 220V,以适配器将 220V 转化为 110V为例)
public class Normal220V {
private int power = 220;
public int getPower() {
return power;
}
}
public interface Target110V {
public void special110VCharge();
}
public class Adapter implements Target110V {
private Normal220V normal;
public Adapter(Normal220V normal) {
this.normal = normal;
}
@Override
public void special110VCharge() {
System.out.println(normal.getPower() / 2 + "v电压");
}
}
public class Client {
public static void main(String[] args) {
Adapter adapter = new Adapter(new Normal220V());
adapter.special110VCharge();
}
}
2.桥接模式
定义:桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
使用场景: 当需要多角度去分类实现对象时,只使用继承会增加大量的类,而且当分类变化时需要修改很多地方,不满足开放-封闭原则,这时就应该考虑使用桥接模式。
特点:桥接在程序上表现为:抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系,桥接模式中的桥接是一个单方向的关系,只能够抽象部分去使用实现部分的对象,而不能反过来。
结构图:
具体例子:
代码:
public abstract class MobileBrand {
protected MobileSoft soft;//聚合
public void setMobileSoft(MobileSoft soft) {
this.soft = soft;
}
public void runSoft() {
soft.run();
}
}
public class HWMobileBrand extends MobileBrand {
@Override
public void runSoft() {
super.runSoft();
System.out.println("on HWMobile");
}
}
public class AppleMobileBrand extends MobileBrand {
@Override
public void runSoft() {
super.runSoft();
System.out.println("on apple mobile");
}
}
public interface MobileSoft {
public void run();
}
public class CalculatingMachine implements MobileSoft {
@Override
public void run() {
System.out.println("run calculating machine");
}
}
public class GameSoft implements MobileSoft {
@Override
public void run() {
System.out.println("run game soft");
}
}
public class Client {
public static void main(String[] args) {
MobileBrand hw = new HWMobileBrand();
hw.setMobileSoft(new GameSoft());
hw.runSoft();
MobileBrand apple = new AppleMobileBrand();
apple.setMobileSoft(new CalculatingMachine());
apple.runSoft();
}
}
3.组合模式
定义:组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
特点:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
代码:
public class Employee {
private String name;
private String dept;
// 关键:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
private List<Employee> subordinates;
public Employee(String name, String dept) {
this.name = name;
this.dept = dept;
subordinates = new ArrayList<Employee>();
}
public void addEmployee(Employee e) {
subordinates.add(e);
}
public List<Employee> getEmployees() {
return subordinates;
}
@Override
public String toString() {
return name + ", dept=" + dept;
}
public static void main(String[] args) {
Employee ceo = new Employee("leijun", "CEO");
Employee headSales = new Employee("-Robert", "Head Sales");
Employee headMarketing = new Employee("-Michel", "Head Marketing");
ceo.addEmployee(headSales);
ceo.addEmployee(headMarketing);
Employee clerk1 = new Employee("----Laura", "Marketing");
Employee clerk2 = new Employee("----Bob", "Marketing");
headMarketing.addEmployee(clerk1);
headMarketing.addEmployee(clerk2);
Employee salesExecutive1 = new Employee("----Richard", "Sales");
Employee salesExecutive2 = new Employee("----Rob", "Sales");
headSales.addEmployee(salesExecutive1);
headSales.addEmployee(salesExecutive2);
System.out.println(ceo);
for (Employee leader : ceo.getEmployees()) {
System.out.println(leader);
for (Employee employer : leader.getEmployees()) {
System.out.println(employer);
}
}
}
}
4.装饰模式:
定义:装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
特点:
使用继承的方式去扩展一个类的功能,会增加类的层级,类的臃肿会加大维护的成本。使用装饰模式扩展一个类的功能。好处在于,如果继承关系是纵向的,那么装饰类则是某个类横向的扩展,并不会影响继承链上的其他类。例如:C extends B , B extends A,如果需要扩展B的功能,可以设计一个B的装饰类,它并不会影响B的子类C。如果采用在B里面增加方法,势必会使B的所有子类结构被改变。
使用场景:
当系统需要增加新功能时,如果修改旧代码,向原来的类中增加新的字段或者方法,会增加原来类的复杂度。如果这些新增的代码仅仅是为了满足仅在某些特定情况下才会执行的特殊需要,那么使用装饰模式就是一种很好的解决方案。它把每个需要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。这样在执行特殊场景时,客户端代码就可以动态的按顺序的装饰对象了。
结构图:
//装饰模式
public interface IWear {
// 定义一个接口,给实现这个接口的对象动态添加职责
public void show();
}
public class FashionPerson implements IWear {
@Override
public void show() {
System.out.println("I want to be very fashion!");
}
}
//装饰的抽象类
public abstract class Clothes implements IWear {
private IWear wear;
public Clothes(IWear wear) {
this.wear = wear;
}
@Override
public void show() {
wear.show();
}
}
public class Tshirt extends Clothes {
public Tshirt(IWear wear) {
super(wear);
// TODO Auto-generated constructor stub
}
@Override
public void show() {
super.show();
System.out.println("i wear a special Tshirt!");
}
}
public class Tie extends Clothes {
public Tie(IWear wear) {
super(wear);
// TODO Auto-generated constructor stub
}
@Override
public void show() {
super.show();
System.out.println("a nice tie match me very will!");
}
}
public class Client {
public static void main(String[] args) {
// 通过构造方法对对象进行包装,在装饰类的show方法中先调用super.show(),
//相当于对原对象进行装饰,这样装饰类(Tshirt、Tie)就可以只关心自己的功能而不需要关系如何被添加到对象链中
IWear person = new FashionPerson();// 使用FashionPerson实例化对象person
Tshirt tshirt = new Tshirt(person);// 使用Tshirt类来包装person
Tie tie = new Tie(tshirt);// 使用Tie来包装tshirt
tie.show();// 最后执行 tie.show()
}
}
5.外观模式
定义:
外观模式(门面模式),简单的来讲就是将多个复杂的业务封装成一个方法,在调用此方法时可以不必关注具体执行了哪些业务,而只关心结果即可。
外观模式是隐藏了系统的复杂性,能够为子系统中的一组接口提供一个统一的接口。客户在使用系统时不必和子系统打交道了,降低了客户和子系统间的耦合。
结构图:
代码:
public class DrinkTea {
SelectTea tea;
SelectWater water;
WashCups cups;
public DrinkTea() {
this.tea = new SelectTea();
this.water = new SelectWater();
this.cups = new WashCups();
}
public void greenTeaPlease() {
tea.selectFGreenTea();
water.selectWaterB();
cups.cleanMyCups();
}
public void blackTeaPlease() {
tea.selectBlackTea();
cups.cleanMyCups();
water.selectWaterA();
}
public static void main(String[] args) {
DrinkTea drinkTea = new DrinkTea();
drinkTea.greenTeaPlease();
}
}
public class SelectTea {
public void selectBlackTea() {
System.out.println("今天喝红茶");
}
public void selectFGreenTea() {
System.out.println("今天喝绿茶");
}
}
public class SelectWater {
public void selectWaterA() {
System.out.println("用农夫山泉");
}
public void selectWaterB() {
System.out.println("用怡宝");
}
}
public class WashCups {
public void cleanMyCups() {
System.out.println("洗下我的茶具");
}
}
外观模式的优点
1、对客户端屏蔽了子系统组件,减少了客户端处理的对象数量,也减少了客户端的代码量。
2、实现了客户端和子系统的松散耦合,使得子系统个变化不会影响到调用它的客户端,只需要改变外观类即可。
3、一个子系统的变化不会影响到另一个子系统,子系统内部变化也不会影响到外观对象。
6.享元模式
定义:享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
关键代码:用 HashMap 存储这些对象。用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
结构图:
代码:
public abstract class WebSite {
abstract void show(User user);
}
public class ConcreteWebSite extends WebSite {
private String webSiteName;
public ConcreteWebSite(String webSiteName) {
this.webSiteName = webSiteName;
}
@Override
void show(User user) {
System.out.println("网站名:" + webSiteName + ",用户名:" + user.getName());
}
}
public class User {
public User(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
}
public class WebSiteFactory {
private Map<String, WebSite> map = new HashMap<String, WebSite>();
public WebSite getWebSiteFromFactory(String key) {
if (map.containsKey(key)) {
return map.get(key);
} else {
WebSite web = new ConcreteWebSite(key);
map.put(key, web);
return web;
}
}
}
public class Client {
public static void main(String[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite boke = factory.getWebSiteFromFactory("博客");
boke.show(new User("小明"));
WebSite taobao = factory.getWebSiteFromFactory("淘宝");
taobao.show(new User("马云"));
WebSite boke2 = factory.getWebSiteFromFactory("博客");
boke2.show(new User("小红"));
}
}
7.代理模式
静态代理和动态代理
定义:为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托类(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
实现过程:
创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。
结构图:
代码:
//买房接口
public interface IBuyHouse {
void buyHouse();
}
//买房人
public class Customer implements IBuyHouse {
@Override
public void buyHouse() {
System.out.println("Customer want to buy a house");
}
}
//房产中介-代理
public class HouseAgency implements IBuyHouse {
private IBuyHouse houseBuyer = new Customer();
@Override
public void buyHouse() {
System.out.println("i am HouseAgency ");
houseBuyer.buyHouse();
System.out.println("i can help you ");
}
public static void main(String[] args) {
HouseAgency agency = new HouseAgency();
agency.buyHouse();
}
}
特点及缺点:
使用静态代理很容易就完成了对一个类的代理操作。但是静态代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。
三、行为型模式
1.观察者模式
定义:观察者模式又称发布订阅模式(Publish/Subscribe),定义了一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象状态发生变化时,会通知所有的观察者对象,使他们能够自动更新自己。
结构图:
代码:(以微信公众号推送文章为例)
public interface Subject {
/***
* 主题或者抽象通知者
*
*/
// 添加观察者
public void registerObserver(Observer o);
// 删除观察者
public void removeObserver(Observer o);
// 通知观察者
public void notifyObserver();
}
public interface Observer {
/**
* 观察者接口,为所有具体观察者提供同一的接口
*/
// 定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。
public void update(String message);
}
public class WechatServer implements Subject {
private List<Observer> observerList = new ArrayList<Observer>();
private String message;
// 以微信公众号为例,关注公众号就可以收到推送消息,取消关注就收不到推送消息
@Override
public void registerObserver(Observer o) {
observerList.add(o);
}
@Override
public void removeObserver(Observer o) {
observerList.remove(o);
}
@Override
public void notifyObserver() {
for (Observer obs : observerList) {
obs.update(message);
}
}
public void setMessage(String message) {
this.message = message;
}
}
public class User implements Observer {
private String name;
public User(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + ":" + message);
}
}
public class Client {
public static void main(String[] args) {
WechatServer server = new WechatServer();
User xiaoming = new User("小明");
User xiaohong = new User("小红");
server.registerObserver(xiaoming);
server.registerObserver(xiaohong);
server.setMessage("文章更新了");
server.notifyObserver();
System.out.println("--------------小红取关,收不到推送的文章了--------------------");
server.removeObserver(xiaoming);
server.notifyObserver();
}
}
特点:
- subject 和 observer之间是松耦合的,各自独立实现。
- subject在发送广播通知的时候,不需要指定具体的observer,observer可以自行决定是否要订阅
- 遵循常用设计原则:高内聚,低耦合
2.模板模式
定义:模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
结构图:
- 抽象模板(AbstractClass)角色有如下责任:
templateMethod():定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法
primitiveMethod():定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤,即templateMethod中会使用primitiveMethod方法的结果。 - 具体模板(Concrete Template)角色又如下责任:
实现父类所定义的一个或多个抽象方法(primitiveMethod),它们是一个顶级逻辑的组成步骤。
每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
代码:
public abstract class AbstractClass {
// 模板方法
public final void templateMethod() {
String result = "templateMethod" + primitiveMethod();
System.out.println(result);
};
// 基本方法的声明(由子类实现)
protected String primitiveMethod() {
return "default";
};
}
public class ConcreteClass extends AbstractClass {
//不同的子类具体实现
@Override
protected String primitiveMethod() {
return "+ConcreteClass!";
}
public static void main(String[] args) {
AbstractClass a = new ConcreteClass();
a.templateMethod();
}
}
3.命令模式
定义:命令模式属于对象的行为模式。命令模式又称为行动(Action)模式或交易(Transaction)模式。
命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。简单说就是将一系列的请求命令封装起来,不直接调用真正执行者的方法。
优点:
命令允许请求的一方和接收请求的一方能够独立演化,从而具有以下的优点:
(1)命令模式使新的命令很容易地被加入到系统里。
(2)允许接收请求的一方决定是否要否决请求。
(3)能较容易地设计一个命令队列。
(4)可以容易地实现对请求的撤销和恢复。
(5)在需要的情况下,可以较容易地将命令记入日志。
结构图:
命令模式涉及到五个角色,它们分别是:
- 客户端(Client)角色:创建一个具体命令(ConcreteCommand)对象并确定其接收者。
- 命令(Command)角色:声明了一个给所有具体命令类的抽象接口。
- 具体命令(ConcreteCommand)角色:定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
- 请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
- 接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
命令模式:通过请求者(invoker)调用命令对象(command),命令对象中调用了命令具体执行者(Receiver)
代码:(以到餐馆点菜为例)
//命令(Command)角色
public interface ICommand {
void execute();
}
//具体命令(ConcreteCommand)角色
public class GongBaoJiDing implements ICommand {
// 调用者、请求者
private Receiver receiver;
public GongBaoJiDing(Receiver receiver) {
this.receiver = receiver;
}
// 具体命令
@Override
public void execute() {
receiver.cookGongBaoJiDing();
}
}
public class YuXiangQieZi implements ICommand {
// 调用者、请求者
private Receiver receiver;
public YuXiangQieZi(Receiver receiver) {
this.receiver = receiver;
}
// 具体命令
@Override
public void execute() {
receiver.cookYuXiangQieZi();
}
}
//请求者(Invoker)角色
public class Invoker {
private List<ICommand> commandList = new LinkedList<ICommand>();
public void order(ICommand command) {
commandList.add(command);
}
public void notifyReceiver() {
for (ICommand command : commandList)
command.execute();
}
}
//接收者(Receiver)角色
public class Receiver {
// 厨师
// 接收者:接收者执行与请求相关的操作
public void cookYuXiangQieZi() {
System.out.println("做鱼香肉丝");
}
public void cookGongBaoJiDing() {
System.out.println("做宫保鸡丁");
}
}
public class Client {
// 顾客,今天想吃鱼香茄子和宫保鸡丁
public static void main(String[] args) {
// 点菜--创建具体命令, 指定厨师--确定命令接收者
Receiver cook = new Receiver();
// 在创建具体命令对象时指定对应的接收者
ICommand qiezi = new YuXiangQieZi(cook);
ICommand jiding = new GongBaoJiDing(cook);
// 服务员, 请求的发送者
Invoker waiter = new Invoker();
waiter.order(qiezi);
waiter.order(jiding);
waiter.notifyReceiver();
}
}
4.状态模式
定义:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
适用场景:在下面的两种情况下均可使用状态模式:
- 一个对象的行为取决于它的状态 , 并且它必须在运行时刻根据状态改变它的行为。
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 ,有多个操作包含这一相同的条件结构。状态模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
结构图:
Context(上下文)
— 定义客户感兴趣的接口
维护一个State子类的实例,这个实例定义当前状态。
State状态
— 定义一个接口以封装与Context的一个特定状态相关的行为。
ConcreteState(State的子类)
— 每一子类实现一个与Context的一个状态相关的行为。
代码:
//销售员工报销,需要主管、经理、总监审批,审批通过之后财务打款。
public class Context {
private State state;
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
process();
}
public void process() {
state.handle(this);
}
}
public abstract class State {
public abstract void handle(Context context);
}
public class SupervisorState extends State {
@Override
public void handle(Context context) {
System.out.println("主管审批通过,下一个经理审批");
context.setState(new ManagerState());
}
}
public class ManagerState extends State {
@Override
public void handle(Context context) {
System.out.println("经理审批通过,下一个总监审批");
context.setState(new DirectorState());
}
}
public class DirectorState extends State {
@Override
public void handle(Context context) {
System.out.println("总监审批通过,我是最后一个审批者");
// 审核通过之后的逻辑
System.out.println("财务打款500元");
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setState(new SupervisorState());
}
}
5.责任链模式
定义: 责任链模式(Chain of Responsibility Pattern),为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
关键:拦截的类都实现统一接口
结构图:
代码:
public abstract class AbstractLogFilter {
public static final int Info = 2;
public static final int Warn = 3;
public static final int Error = 4;
public int level;
private AbstractLogFilter filter;
public void setfilter(AbstractLogFilter filter) {
this.filter = filter;
}
public void recordLog(int logLevel) {
if (logLevel <= level) {
this.writeMessage();
} else {
if (filter != null) {
filter.recordLog(logLevel);
}
}
}
public void writeMessage() {
};
}
public class InfoLog extends AbstractLogFilter {
public InfoLog(int level) {
this.level = level;
}
@Override
public void writeMessage() {
System.out.println("This is a info log!");
}
}
public class WarnLog extends AbstractLogFilter {
public WarnLog(int level) {
this.level = level;
}
@Override
public void writeMessage() {
System.out.println("This is a warn log!");
}
}
public class ErrorLog extends AbstractLogFilter {
public ErrorLog(int level) {
this.level = level;
}
@Override
public void writeMessage() {
System.out.println("This is a error log!");
}
}
public class Client {
public static void main(String[] args) {
AbstractLogFilter infoLog = new InfoLog(AbstractLogFilter.Info);
AbstractLogFilter warnLog = new WarnLog(AbstractLogFilter.Warn);
AbstractLogFilter errlog = new ErrorLog(AbstractLogFilter.Error);
infoLog.setfilter(warnLog);
warnLog.setfilter(errlog);
infoLog.recordLog(AbstractLogFilter.Error);
}
}
6.中介者模式
定义: 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。
使用场景:
对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
结构图:
代码:
//抽象中介者--联合国组织
public abstract class UnitedNations {
abstract void declare(String message, Country country);
}
//具体抽象者--联合国安理会
public class UnitedNationsSecuritycouncil extends UnitedNations {
private USA usa;
private Iraq iraq;
public void setUsa(USA usa) {
this.usa = usa;
}
public void setIraq(Iraq iraq) {
this.iraq = iraq;
}
@Override
public void declare(String message, Country country) {
if (country == usa) {
iraq.getMessage(message);
} else {
usa.getMessage(message);
}
}
}
public abstract class Country {
protected UnitedNations mediator;
public Country(UnitedNations mediator) {
this.mediator = mediator;
}
abstract void declare(String message);
abstract void getMessage(String message);
}
public class USA extends Country {
public USA(UnitedNations mediator) {
super(mediator);
}
@Override
void declare(String message) {
mediator.declare(message, this);
}
@Override
void getMessage(String message) {
System.out.println("美国获得对方消息:" + message);
}
}
public class Iraq extends Country {
public Iraq(UnitedNations mediator) {
super(mediator);
// TODO Auto-generated constructor stub
}
@Override
void declare(String message) {
mediator.declare(message, this);
}
@Override
void getMessage(String message) {
System.out.println("伊朗获得对方消息:" + message);
}
}
public class Client {
public static void main(String[] args) {
UnitedNationsSecuritycouncil un = new UnitedNationsSecuritycouncil();
USA usa = new USA(un);
Iraq iraq = new Iraq(un);
un.setUsa(usa);
un.setIraq(iraq);
usa.declare("不准研究核武器,否则发动战争!");
iraq.declare("没有核武器,也不怕战争!");
}
}
使用场景
1、系统中对象间存在较为复杂引用,导致依赖关系和结构混乱而无法复用的情况。
2、想通过一个中间类来封装多个类的行为,但是又不想要太多的子类。
优点
1、松散耦合、将多个对象之间的联系紧耦合封装到中介对象中,做到松耦合。不会导致一动牵全身。
2、将多个对象之间的交互联系集中在中介对象中。发送变化仅需修改中介对象即可、提供系统的灵活性、使同事对象独立而易于复用。
3、符合迪米特原则。就是说一个对象应当对其他对象有尽可能少的了解。减少各个对象之间的了解。
缺点
如果各个同事间的交互非常多并且复杂情况下,都交给中介者会导致中介者变得十分复杂,不易维护和管理。
7.访问者模式
结构图:
其中:
(1)Visitor:(代码中的AccountBookViewer类) 接口或者抽象类,它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法数理论上来讲与元素个数是一样的,因此,访问者模式要求元素的类族要稳定,如果经常添加、移除元素类,必然会导致频繁地修改Visitor接口,如果这样则不适合使用访问者模式。
(2)ConcreteVisitor1、ConcreteVisitor2:(代码中的BOSS、CPA类类)具体的访问类,它需要给出对每一个元素类访问时所产生的具体行为。
(3)Element:(代码中的Bill类)元素接口或者抽象类,它定义了一个接受访问者的方法(Accept),其意义是指每一个元素都要可以被访问者访问。
(4)ConcreteElementA、ConcreteElementB:(代码中的ConsumeBill、IncomeBill类)具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
(5)ObjectStructure:(代码中的AccountBook类)定义当中所说的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素供访问者访问。
代码:
//账单查看者接口,相当visitor
public interface AccountBookViewer {
// 查看收入账单
void viewIncomeBill(IncomeBill bill);
// 查看消费账单
void viewConsumeBill(ConsumeBill bill);
}
public class BOSS implements AccountBookViewer {
private double totalIncome;
private double totalConsume;
@Override
public void viewIncomeBill(IncomeBill bill) {
totalIncome += bill.getAmount();
}
@Override
public void viewConsumeBill(ConsumeBill bill) {
totalConsume += bill.getAmount();
}
public double getTotalIncome() {
return totalIncome;
}
public double getTotalConsume() {
return totalConsume;
}
}
public class CPA implements AccountBookViewer {
@Override
public void viewIncomeBill(IncomeBill bill) {
System.out.println("注会查看收入交税了没。");
}
@Override
public void viewConsumeBill(ConsumeBill bill) {
if (bill.getItem().equals("工资")) {
System.out.println("注会查看工资是否交个人所得税。");
}
}
}
public interface Bill {
void accept(AccountBookViewer view);
}
public class IncomeBill implements Bill {
private double amount;
private String item;
public IncomeBill(double amount, String item) {
this.amount = amount;
this.item = item;
}
public double getAmount() {
return amount;
}
@Override
public void accept(AccountBookViewer viewer) {
viewer.viewIncomeBill(this);
}
}
public class ConsumeBill implements Bill {
private double amount;
private String item;
public ConsumeBill(double amount, String item) {
this.amount = amount;
this.item = item;
}
public double getAmount() {
return amount;
}
public String getItem() {
return item;
}
@Override
public void accept(AccountBookViewer viewer) {
viewer.viewConsumeBill(this);
}
}
//账本类,访问者模式例子中的对象结构-ObjectStruture
public class AccountBook {
private List<Bill> accountBook = new ArrayList<Bill>();
public void addBill(Bill bill) {
accountBook.add(bill);
}
// 供账本的查看者查看账本
public void show(AccountBookViewer viewer) {
for (Bill bill : accountBook) {
bill.accept(viewer);
}
}
}
public class Client {
public static void main(String[] args) {
AccountBook billBook = new AccountBook();
billBook.addBill(new IncomeBill(1000, "销售"));
billBook.addBill(new IncomeBill(2000, "广告"));
billBook.addBill(new ConsumeBill(1000, "工资"));
billBook.addBill(new ConsumeBill(3000, "奖金"));
AccountBookViewer boss = new BOSS();
billBook.show(boss);
System.out.println(((BOSS) boss).getTotalConsume());
System.out.println(((BOSS) boss).getTotalIncome());
AccountBookViewer cpa = new CPA();
billBook.show(cpa);
}
}
代码分析:上面的代码中,可以这么理解,账本以及账本中的元素是非常稳定的,这些几乎不可能改变,而最容易改变的就是访问者这部分。
访问者模式最大的优点就是增加访问者非常容易,我们从代码上来看,如果要增加一个访问者,你只需要做一件事即可,那就是写一个类,实现AccountBookViewer接口,然后就可以直接调用AccountBook的show方法去访问账本了。
如果没使用访问者模式,一定会增加许多if else,而且每增加一个访问者,你都需要改你的if else,代码会显得非常臃肿,而且非常难以扩展和维护。
访问者模式中使用的是伪动态双分派,所谓的动态双分派就是在运行时依据两个实际类型去判断一个方法的运行行为,而访问者模式实现的手段是进行了两次动态单分派来达到这个效果。动态分派可以在这里当做重写(override)去理解,它们之间有着密切的关联。
回到上面例子当中账本类中的accept方法
for (Bill bill : billList) {
bill.accept(viewer);
}
这里就是依据biil和viewer两个实际类型决定了viewConsumeBill或viewIncomeBill方法的版本,从而决定了accept方法的动作。
分析accept方法的调用过程:
1.当调用accept方法时,根据bill的实际类型决定是调用ConsumeBill还是IncomeBill的accept方法。
2.这时accept方法的版本已经确定,假如是ConsumeBill,它的accept方法是调用下面这行代码。
public void accept(AccountBookViewer viewer) {
viewer.viewConsumeBill(this);
}
此时的this是ConsumeBill类型,所以对应于AccountBookViewer接口的viewConsumeBill(ConsumeBill bill)方法,此时需要再根据viewer的实际类型确定viewConsumeBill方法的版本,如此一来,就完成了动态双分派的过程。
以上的过程就是通过两次动态双分派,第一次对accept方法进行动态分派,第二次对viewConsumeBill(类图中的visit方法)方法进行动态分派,从而达到了根据两个实际类型确定一个方法的行为的效果。
而原本我们的做法,通常是传入一个接口,直接使用该接口的方法,此为动态单分派,就像策略模式一样。在这里,show方法传入的viewer接口并不是直接调用自己的view方法,而是通过bill的实际类型先动态分派一次,然后在分派后确定的方法版本里再进行自己的动态分派。
定义:访问者模式(visitor)表示一个作用于某个对象结构中的各元素的操作。它可以在不改变各元素类的情况下增加对这些元素类的新操作。访问者模式实现了数据结构和对这些数据结构的操作之间的解耦。
使用场景:
1.对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
8.策略模式:
定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
结构图:
代码:
//父类
public abstract class Strategy {
public abstract int countStrategy();
}
//算法A
public class StrategyA extends Strategy {
@Override
public int countStrategy() {
System.out.println("算法A");
return 0;
}
}
//算法B
public class StrategyB extends Strategy {
@Override
public int countStrategy() {
System.out.println("算法B");
return 0;
}
}
//首先声明一个Strategy 对象,通过构造方法,传入具体的策略或算法,getResultFromStrategy()方法的功能为根据策略的不同获得计算结果
public class Context {
private Strategy strategy;
public Context(Strategy strategyType) {
strategy = strategyType;
}
public int getResultFromStrategy() {
return strategy.countStrategy();
}
}
策略模式和简单工厂的区别:
- 用途不一样
工厂是创建型模式,它的作用就是创建对象;
策略是行为型模式,它的作用是让一个对象在许多行为中选择一种行为; - 关注点不一样
一个关注对象创建
一个关注行为的封装 - 解决不同的问题
工厂模式是创建型的设计模式,它接受指令,创建出符合要求的实例;它主要解决的是资源的统一分发,将对象的创建完全独立出来,让对象的创建和具体的使用客户无关。主要应用在多数据库选择,类库文件加载等。
策略模式是为了解决的是策略的切换与扩展,更简洁的说是定义策略族,分别封装起来,让他们之间可以相互替换,策略模式让策略的变化独立于使用策略的客户。
工厂模式相当于黑盒子,策略模式相当于白盒子;
策略模式的应用:
出行方式,自行车、汽车等,每一种出行方式都是一个策略;
商场促销方式,打折、满减等
9.备忘录模式
定义:备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
结构图:
其中:
Originator(发起人):负责创建一备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。
Memento(备忘录):负责存储Originator对象的的内部状态。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能看到一个宽接口,允许它访问返回到先前状态所需要的所有数据。
Caretaker(备忘录管理者):负责保存备忘录但不能对备忘录的内容进行操作和修改。
代码:
//Originator类
public class GameRole {
private String health;
private String attack;
public void initState() {
this.health = "100";
this.attack = "100";
}
public void afterAttackBoss() {
this.health = "0";
this.attack = "0";
}
public void show() {
System.out.println("health:" + health);
System.out.println("attack:" + attack);
}
public Memento saveGameState() {
return new Memento(health, attack);
}
public void restoreGameState(Memento memento) {
this.health = memento.getHealth();
this.attack = memento.getAttack();
}
}
//Memento类
public class Memento {
private String health;
private String attack;
public Memento(String health, String attack) {
this.health = health;
this.attack = attack;
}
public String getHealth() {
return health;
}
public void setHealth(String health) {
this.health = health;
}
public String getAttack() {
return attack;
}
public void setAttack(String attack) {
this.attack = attack;
}
}
//Caretaker管理备忘录的类
public class GameStateCaretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
public class Client {
public static void main(String[] args) {
// 初始状态
GameRole hero = new GameRole();
hero.initState();
System.out.println("begin---");
hero.show();
// 为什么不直接new一个Memento保存状态:为了符合迪米特原则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。
// 如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者(GameStateCaretaker)转发这个调用。
// 如果直接new Memento,就暴露了GameRole中的具体实现
GameStateCaretaker caretaker = new GameStateCaretaker();
// 保存状态
caretaker.setMemento(hero.saveGameState());
// 状态改变
hero.afterAttackBoss();
System.out.println("afterAttackBoss---");
hero.show();
// 恢复状态
hero.restoreGameState(caretaker.getMemento());
System.out.println("after restore---");
hero.show();
}
}