1.概念:
软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。
2.设计模式的分类:
(1)创建型模式
用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
(2)结构性模式
用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
(3)行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
3.单例模式的结构:
(1)结构:
1.单例类:只能创建一个实例的类
2.访问类:使用单例类
(2)实现模式:
①饿汉式:类加载就会导致该单实例对象被创建
public class Test01 { public static void main(String[] args) { Student student=Student.getInstance(); student.show(); } } class Student{ private Student(){ } private static Student student=new Student(); public static Student getInstance(){ return student; } public void show(){ System.out.println("hello world"); } }
②懒汉式(双重检查锁)
public class Test02 { public static void main(String[] args) { person p=person.getInstance(); } } class person{ private person(){}; private static volatile person p; public static person getInstance(){ if (p==null){ synchronized (person.class){ if (p==null){ p=new person(); } } } return p; } } //volatile的作用:1.可以防止jvm指令重排优化2.可以保证可见性和有序性
4.工厂模式:
(1)简单工厂:
简单工厂模式的核心是定义一个创建对象的接口,将对象的创建和本身的业务逻辑分离,降低系统的耦合度,使得两个修改起来相对容易些,当以后实现改变时,只需要修改工厂类即可。
//手机用法 public abstract class Phone { public abstract void call(); } //手机型号 public class HuaWeiPhnoe extends Phone{ @Override public void call() { System.out.println("HuaWei手机打电话"); } } 手机型号 public class VivoPhone extends Phone{ public void call(){ System.out.println("Vivo手机打电话"); } } //手机工厂 public class PhoneFactory { public Phone getPhone(String type) { if (type.equals("HuaWei")) { return new HuaWeiPhnoe(); } else if (type.equals("Vivo")) { return new VivoPhone(); } return null; } //测试类 public class Test { public static void main(String[] args) { Phone phone = new PhoneFactory().getPhone("Vivo"); phone.call(); } }
优缺点:
优点:简单工厂模式提供专门的工厂类用于创建对象,实现了对象创建和使用的职责分离,客户端不需知道所创建的具体产品类的类名以及创建过程,只需知道具体产品类所对应的参数即可,通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点:不符合“开闭原则”,每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
(2)工厂方法:
工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。
缺点在于,每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
//抽象工厂 public abstract class AbstractFactory { public abstract Phone create(); } //具体工厂 public class VivoFactory extends AbstractFactory{ @Override public Phone create() { return new VivoPhone(); } } //手机方法 public abstract class Phone { public abstract void call(); } //手机型号 public class VivoPhone extends Phone{ @Override public void call() { System.out.println("Vivo手机打电话"); } //测试类 public class Test { public static void main(String[] args) { Phone p = new VivoFactory().create(); p.call(); } }
5.代理模式:
(1)定义:
对其他对象提供一个代理控制对这个对象的访问
(2)作用:
代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。
代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展。
(3)构成:
抽象角色(subject):通过接口或抽象类生命真实角色实现业务的方法。角色具有的行为放入该接口或者抽象总类。
代理角色(proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
(4)静态代理:
①定义:
静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法。
②案例:
//抽象角色 public interface SetHouse { public void setHouse(); } //代理角色 public class MIddlePeople { private Houser house; public MIddlePeople(Houser houser) { this.house = houser; } public void setHouse() { System.out.println("中介正在看房"); house.setHouse(); System.out.println("中介正在签合同"); } } //真实角色 public class Houser implements SetHouse{ @Override public void setHouse() { System.out.println("正在租房"); } } //测试 public class Test { public static void main(String[] args) { MIddlePeople mp = new MIddlePeople(new Houser()); mp.setHouse(); } }
③缺点:
代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。
(5)动态代理(JDK代理)
①定义:
动态代理就是在程序运行时通过反射机制动态创建的
②案例:
//抽象角色 public interface SetHouse { public void setHouse(); } //代理类: public class JdkProxy { private SetHouse setHouse; public JdkProxy(SetHouse setHouse) { this.setHouse=setHouse; } public SetHouse getSetHouse() { //ClassLoader loader:指定当前目标对象使用类加载器,写法固定 //Class<?>[] interfaces:目标对象实现的接口的类型,写法固定 //InvocationHandler h:提供的执行被代理角色方法的方法。 ClassLoader classLoader=setHouse.getClass().getClassLoader(); Class[] interfaces=setHouse.getClass().getInterfaces(); InvocationHandler h=new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("你好,哈哈"); Object result=method.invoke(setHouse); System.out.println("再见,哈哈"); return result; } }; return (SetHouse) Proxy.newProxyInstance(classLoader,interfaces,h); } } //真实角色 public class Houser implements SetHouse { @Override public void setHouse() { System.out.println("正在租房"); } } //测试类 public class Test { public static void main(String[] args) { JdkProxy jdkProxy = new JdkProxy(new Houser()); SetHouse setHouse = jdkProxy.getSetHouse(); setHouse.setHouse(); } }
③缺点:
静态代理和JDK代理有一个共同的缺点,目标对象必须实现一个或多个接口。
(5)CgLib代理
①前提条件:
1.引入jar包:cglib
2.目标类不能为Final
3.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
4.目标对象没有实现类接口
②案例
//引入jar包 <dependencies> <dependency> <groupId>repMaven.cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies>
//真实角色 public class House { public void sethouse(){ System.out.println("购买房屋"); } } //代理角色 public class ProxyFactory implements MethodInterceptor { private Object target; public ProxyFactory(Object target) { this.target = target; } public Object getTarget() { Enhancer en=new Enhancer(); en.setSuperclass(target.getClass()); en.setCallback(this); return en.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("介绍房屋"); Object result=method.invoke(target,objects); System.out.println("成交"); return result; } } //测试类 public class Test { public static void main(String[] args) { House house = new House(); House proxy= (House) new ProxyFactory(house).getTarget(); proxy.sethouse(); } }
6.模板方法:
(1)定义:
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
(2)意图:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定功能。
(3)案例:
//算法骨架 public abstract class Chaocai { public final void make() { selectFood(); clean(); cook(); tiaoLiao(); finish(); } public abstract void selectFood(); public void clean(){ System.out.println("清洗食材"); }; public void cook(){ System.out.println("起锅烧油"); }; public abstract void tiaoLiao(); public void finish(){ System.out.println("盛盘"); }; } //子类继承 public class TomatoEgg extends Chaocai{ @Override public void selectFood() { System.out.println("选择番茄鸡蛋"); } @Override public void tiaoLiao() { System.out.println("盐和酱油"); } } //测试 public class Test { public static void main(String[] args) { Chaocai chaocai = new TomatoEgg(); chaocai.make(); } }
(4)优缺点:
优点:1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
(5)使用场景:
1、有多个子类共有的方法,且逻辑相同。
2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
7.适配器模式:
(1)定义:
适配器模式把一个类的接口变换成客户端的所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能 在一起工作
(2)类适配器:
①实现方式:
Adaptee:适配者类,它是需要被访问的、需要被适配的组件. AC220V
Target:目标接口,当前系统业务所使用的接口,可以是抽象类或接口. DC5V
Adapter:适配器类,通过继承和实现目标接口,让客户端按照目标接口的方法访问适配者
Client:客户端,适配器的使用者
②案例:
//Adaptee:适配者类,它是需要被访问的、需要被适配的组件. public class AC220 { public int output220V() { int output = 220; return output; } } //Target:目标接口,当前系统业务所使用的接口,可以是抽象类或接口. public interface DC5 { int output5V(); } //Adapter:适配器类,通过继承和实现目标接口,让客户端按照目标接口的方法访问适配者 public class PowerAdapter extends AC220 implements DC5 { @Override public int output5V() { int output = output220V(); return (output / 44); } } //测试类 public class TestClassAdapter { public static void main(String[] args) { DC5 dc5 = new PowerAdapter(); System.out.println("输出电流:" + dc5.output5V() + "V"); } }
③优缺点:
优点:由于Adapter继承了Adaptee类,所以它可以根据需求重写Adaptee类的方法,使得Adapter的灵活性增强了。
缺点:因为java单继承的缘故,Target类必须是接口,以便于Adapter去继承Adaptee并实现Target,完成适配的功能,但这样就导致了Adapter里暴露了Adaptee类的方法,使用起来的成本就增加了。
(3)对象适配器模式:
①实现方式:
Adaptee
:适配者类,它是需要被访问的、需要被适配的组件
Target
:目标接口,当前系统业务所使用的接口,可以是抽象类或接口
Adapter
:适配器类,通过聚合和实现目标接口,让客户端按照目标接口的方法访问适配者
Client
:客户端,适配器的使用者
②案例:
//Adaptee:适配者类,它是需要被访问的、需要被适配的组件 public class AC220 { public int output220V() { int output = 220; return output; } } //Target:目标接口,当前系统业务所使用的接口,可以是抽象类或接口 public interface DC5 { int output5V(); } //Adapter:适配器类,通过聚合和实现目标接口,让客户端按照目标接口的方法访问适配者 public class PowerAdapter implements DC5 { private AC220 ac220; public PowerAdapter(AC220 ac220) { this.ac220 = ac220; } @Override public int output5V() { int output = this.ac220.output220V(); return (output / 44); } } //测试 public class TestObjectAdapter { public static void main(String[] args) { AC220 ac220 = new AC220(); PowerAdapter powerAdapter = new PowerAdapter(ac220); System.out.println("输出电流:" + powerAdapter.output5V() + "V"); } }
③优点:
根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承Adaptee的局限性问题,也不再要求Target必须是接口。使用成本更低,更灵活。
8.观察者模式:
(1)定义:
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
(2)结构:
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
(3)案例:
//抽象观察者角色: public interface UserInterface { public void getMsg(String msg); } //观察者角色: public class User implements UserInterface { private String name; public User(String name) { this.name = name; } public void getMsg(String msg) { System.out.println(name+"接受到公众号发送的消息:"+msg); } } //抽象公众号: public interface PublicHaoInterface { public void add(UserInterface userInterface); public void remove(UserInterface userInterface); public void setMsg(String msg); public void qunfa(); } //公众号: public class AAAPublicHao implements PublicHaoInterce { List<UserInterface> list=new ArrayList<UserInterface>(); private String msg; public void add(UserInterface userInterface) { list.add(userInterface); } public void remove(UserInterface userInterface) { list.remove(userInterface); } public void setMsg(String msg) { this.msg=msg; } public void qunfa() { for(UserInterface userInterface:list){ userInterface.getMsg(msg); } } }
(3)优缺点:
优点:降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。 目标与观察者之间建立了一套触发机制。
缺点:目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
9.策略模式:
(1)定义:
类中经常改变或者可能改变的部分提取为作为一个抽象策略接口类,然后在类中包含这个对象的实例,这样类实例在运行时就可以随意调用实现了这个接口的类的行为
(2)结构:
(1)环境类(Context):通过 ConcreteStrategy 具体策略类来配置,持有 Strategy 对象并维护对Strategy 对象的引用。可定义一个接口来让 Strategy 访问它的数据。
(2)抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy 定义的算法。
(3)具体策略类(ConcreteStrategy): Strategy 接口的具体算法。
(3)案例
public interface Pay { public void pay(); } public class payWX implements Pay{ public void pay() { System.out.println("微信支付"); } } public class PayZFB implements Pay{ @Override public void pay() { System.out.println("支付宝支付"); } } public class MeiTuan { private Pay pay; public MeiTuan(Pay pay){ this.pay = pay; } public void setPay() { pay.pay(); } } public class Test { public static void main(String[] args) { MeiTuan meiTuan = new MeiTuan(new PayZFB()); meiTuan.setPay(); } }
(4)优缺点:
优点:1、算法可以自由切换(策略类自由切换)。
2、避免使用多重条件判断。
3、扩展性良好(符合开闭原则)。
缺点: 1、策略类会增多。
2、所有策略类都需要对外暴露。
3、客户端必须知道所有的策略类,才能确定要调用的策略类。
(5)使用场景:
-
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
-
一个系统需要动态地在几种算法中选择一种。
-
如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。