设计模式(结构型)
结构型设计模式关注如何将现有的类或对象组织在一起形成更加强大的结构。
类/对象适配器模式(Adapter Pattern)
-
定义:将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
-
类适配器:通过多重继承实现适配器,使得适配器既具备原始类的功能,又可以实现目标接口的功能(不推荐使用)
public class TestSupplier{ //手机供应商 public String doSupply(){ return "iPhone 14 Pro"; } }
public interface Target{ //现在的手机供应商,并不是test方法所需要的那种类型 String supply(); }
public class TestAdapter extends TestSupplier implements Target{ //让适配器继承TestSupplier并且实现Target接口 @Override public String supply(){ //接着实现supply方法,直接使用TestSupplier提供的实现 return super.doSupply(); } }
public class Main{ public static void main(String[] args){ TestAdapter adapter = new TestAdapter(); test(adapter); //我们没有 } public static void test(Target target){ //需要调用test方法,但是test方法需要Target类型的手机供应商 System.out.println("成功得到:" + target.supply()); } }
-
对象适配器:通过组合一个原始类的实例和目标接口的实例来实现适配器,使得适配器可以同时具备原始类和目标接口的功能(推荐使用)
public class TestAdapter implements Target{ //现在不再继承TestSupplier,仅实现了Target TestSupplier supplier; public TestAdapter(TestSupplier supplier){ this.supplier = supplier; } @Override public String supply(){ return supplier.doSupply(); } }
桥接模式(Bridge Patten)
-
定义:将抽象部分与它的实现部分解耦,使得两者都能够独立变化
public interface Size{ //分大杯小杯中杯 String getSize(); }
public abstract class AbstractTea{ protected Size size; //尺寸作为桥接属性存放在类中 protected AbstractTea(Size size){ //在构造时需要知道尺寸属性 this.size = size; } public abstract String getType(); //具体类型依然是由子类决定 }
public abstract class RefinedAbstractTea extends AbstractTea{ protected RefinedAbtractTea(Size size){ super(size); } public String getSize(){ //添加尺寸维度获取方式 return size.getSize(); } }
单独为Size创建子类:
public class Large implements Size{ @Override public String getSize(){ return "大杯"; } }
需要大杯啵啵芋圆奶茶:
public class KissTea extends RefinedAbstractTea{ //创建一个啵啵芋圆奶茶的子类 protected KissTea(Size size){ //在构造时需要指定具体的大小实现 super(size); } @Override public String getType(){ return "啵啵芋圆奶茶"; //返回奶茶类型 } }
public static void main(String[] args){ KissTea tea = new KissTea(new Large()); System.out.println(tea.getType()); System.out.println(tea.getSize()); }
组合模式(Composite Patten)
-
定义:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。
//首先创建一个组件对象,组件可以包含组件,组件有自己的业务方法 public abstract class Component{ public abstract void addComponent(Component component); //添加子组件 public abstract void removeComponent(Component component); //删除子组件 public abstract Component getChild(int index); //获取子组件 public abstract void test(); //执行对应的业务方法,比如修改文件名称 }
两种实现类:
public class Directory extends Component{ //目录可以包含多个文件或目录 List<Component> child = new ArrayList<>(); //这里我们使用List来存放目录中的子组件 @Override public void addComponent(Component component){ child.add(component); } @Override public void removeComponent(Component component){ child.remove(component); } @Override public Component getChild(int index){ return child.get(index); } @Override public avoid test(){ child.forEach(Component::test); //将继续调用所有子组件的test方法执行业务 } }
public class File extends Component{ //文件就相当于是树叶,无法再继续添加子组件了 @Override public void addComponent(Component component){ throw new UnsupportedOperationException(); //不支持这些操作了 } @Override public void removeComponent(Component component){ throw new UnsupportedOperationException(); } @Override public Component getChild(int index){ throw new UnsupportedOperationException(); } @Override public avoid test(){ System.out.println("文件名称修改成功!" + this); //具体的名称修改操作 } }
public static void main(String[] args){ Directory outer = new Directory(); //新建一个外层目录 Directory inner = new Directory(); //新建一个内层目录 outer.addComponent(inner); outer.addComponent(new File()); //在内层目录和外层目录都添加点文件 inner.addComponent(new File()); inner.addComponent(new File()); outer.test(); //开始执行文件名称修改操作 }
装饰模式(Decorator Pattern)
-
定义:动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
public abstract class Base{ //顶层抽象类,定义了一个test方法执行业务 public abstract void test(); }
public class BaseImpl extends Base{ @Override public void test(){ System.out.println("我是业务方法"); //具体业务方法 } }
public class Decorator extends Base{ //装饰者需要将装饰目标组合到类中 protected Base base; public Decorator(Base base){ this.base = base; } @Override public void test(){ base.test(); //这里暂时还是使用目标的原本方法实现 } }
public class DecoratorImpl extends Decorator{ //装饰实现 public DecoratorImpl(Base base){ super(base); } @Override public void test(){ //将原本的方法进行装饰,我们可以在前后都去添加额外的操作 System.out.println("装饰方法:我是操作前逻辑"); super.test(); System.out.println("装饰方法,我是操作后逻辑"); } }
public static void main(String[] args){ Base base = new BaseImpl(); Decorator decorator = new DecoratorImpl(base); //将Base实现装饰一下 Decorator outer = new DecoratorImpl(decorator); //装饰者还可以嵌套 decorator.test(); outer.test(); }
代理模式(Proxy Pattern)
-
定义:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
-
静态代理:在编译期就生成
-
动态代理:在Java运行时动态生成
-
代理(Proxy)模式分为三种角色:
-
抽象出题类(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法
-
真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
-
代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
-
-
静态代理(火车站卖票)
-
抽象主题类/接口(Subject)
public interface SellTickets{ void sell(); }
-
具体主题类(Real Subject)
public class TrainStation implements SellTickets{ public void sell(){ System.out.println("火车站卖票"); } }
-
代理类(Proxy)
public class ProcyPoint implements SellTickets{ //声明火车站类对象 private TrainStation trainStation = new TrainStation(); public void sell(){ System.out.println("代售点收取一些服务费用"); trainStation.sell(); } }
-
客户端调用代理类(Client)
public class Client{ public static void main(String[] args){ //创建代理对象 ProxyPoint p = new ProxyPoint(); //调用方法进行卖票 p.sell(); } }
-
-
JDK动态代理(火车站卖票)
-
要求必须定义接口,对接口进行代理
-
Java中提供了一个动态代理类Proxy,Proxy并不是代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象
-
执行流程:
-
在测试类中通过代理对象调用sell()方法
-
根据多态性的特性,执行的是代理类($Proxy0)中的sell()方法
-
代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
-
invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
-
-
抽象主题类/接口(Subject)
public interface SellTickets{ void sell(); }
-
具体主题类(Real Subject)
public class TrainStation implements SellTickets{ public void sell(){ System.out.println("火车站卖票"); } }
-
获取代理对象的工厂类(ProxyFactory)
public class ProxyFactory{ //声明目标对象 private TrainStation station = new TrainStation(); //获取代理对象的方法 public SellTickets getProxyObjects(){ //返回代理对象即可 /* ClassLoader loader:类加载器,用于加载代理类,可以通过目标对象获取类加载器 Class<?>[] interfaces:代理类实现的接口的字节码对象 InvocationHandler h:代理对象的调用处理程序 */ SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance( station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler(){ /* Object proxy:代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不用 Method method:对接口中的方法进行封装的method对象 Object[] args:调用方法的实际参数 返回值:方法的返回值 */ public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ System.out.println("代售点收取一定的服务费用(JDK动态代理)"); //执行目标对象的方法 Object obj = method.invoke(station,args); return obj; } } ); return proxyObject; } }
-
客户类(Client)
public class Client{ public static void main(String[] args){ //获取代理对象 //1.创建代理工厂对象 ProxyFactory factory = new ProxyFactory(); //2.使用factory对象的方法获取代理对象 SellTickets proxyObject = factory.getProxyObject(); //调用卖票的方法 proxyObject.sell(); } }
-
-
CGLIB动态代理:是一个功能强大、高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
-
具体主题类(Real Subject)
public class TrainStation{ public void sell(){ System.out.println("火车站卖票"); } }
-
获取代理对象的工厂类(ProxyFactory)
public class ProxyFactory implements MethodInterceptor{ //声明目标对象 private TrainStation station = new TrainStation(); //获取代理对象方法 public TrainStation getProxyObject(){ //创建Enhancer对象,类似于JDK代理中的Proxy类 Enhancer enhancer = new Enhancer(); //设置父类的字节码对象(指定父类) enhancer.setSuperclass(TrainStaion.class); //设置回调函数 enhancer.setCallback(this); //创建代理对象 TrainStation proxyObject = (TrainStation) enhancer.create(); return proxyObject; } public Object intercept(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws Exception{ System.out.println("代售点收取一定的服务费用(CGLIB动态代理)") //要调用目标对象的方法 Object obj = method.invoke(station,objects); return obj; } }
-
客户端(Client)
public class Client{ public static void main(String[] args){ //创建代理工厂类 ProxyFactory factory = new ProxyFactory(); //获取代理对象 TrainStation proxyObject = factory.getProxyObject(); //调用代理对象中的sell()方法卖票 proxyObject.sell(); } }
-
-
三种代理的对比
-
JDK和CGLIB代理
-
使用CGLIB实现动态代理,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLIB不能对声明为final的类或者方法进行代理,因为CGLIB原理是动态生成被代理的子类。
-
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLIB代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLIB。
-
有接口就使用JDK动态代理,如果没有接口则使用CGLIB代理。
-
-
动态代理和静态代理
-
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
-
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护复杂度。而动态代理不会出现该问题。
-
-
外观模式(Facade Patten)
-
是迪米特法则的典型应用
-
定义:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得子系统更加容易使用。
-
外观(Facade)模式包含以下主要角色:
-
外观(Facade)角色:为多个子系统提供一个共同的接口
-
子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
-
-
案例(智能家电控制)
-
Light电灯类
public class Light{ //开灯 public void on(){ System.out.println("打开电灯"); } //关灯 public void off(){ System.out.println("关闭电灯"); } }
-
TV电视机类
public class TV{ public void on(){ System.out.println("打开电视机"); } public void off(){ System.out.println("关闭电视机"); } }
-
AirCondition空调类
public class AirCondition{ public void on(){ System.out.println("打开空调"); } public void off(){ System.out.println("关闭空调"); } }
-
SmartAppliancesFacade:外观类,用户主要和该类对象进行交互
public class SmartAppliancesFacade{ //聚合电灯、电视机、空调对象 private Light light; private TV tv; private AirCondition airCondition; public SmartAppliancesFacade(){ light = new Light(); tv = new TV(); airCondition = new AirCondition(); } //通过语音控制 public void say(String message){ if(message.contains("打开")){ on(); }else if(message.contains("关闭")){ off(); }else { System.out.println("我还听不懂你说的"); } } //一键打开功能 private void on(){ light.on(); tv.on(); airCondition.on(); } //一键关闭功能 private void off(){ light.off(); tv.off(); airCondition.off(); } }
-
客户端类(Client)
public class Client{ public static void main(String[] args){ //创建智能音箱对象 SmartAppliancesFacade facade = new SmartAppliancesFacade(); //控制家电 facede.say("打开家电"); System.out.println("============="); facade.say("关闭家电"); } }
-
享元模式(Flyweight Patten)
-
定义:运用共享技术有效地支持大量细粒度对象的复用
-
享元(Flyweight)模式中存在以下两种状态:
-
内部状态,即不会随着环境的改变而改变的可共享部分
-
外部状态,指随着环境改变而改变的不可以共享的部分
-
-
享元(Flyweight)模式主要有以下角色:
-
抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽像享元类中声明了具体享元公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)
-
具体享元(Concrete Flyweight)角色:它实现了抽象享元类,成为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
-
非享元(UnShared Flyweight)角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
-
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象。如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
-
-
案例(俄罗斯方块)
-
俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
public abstract class AbstractBox{ //获取图形的方法 public abstract String getShape(); //显示图形及颜色 public void display(String color){ System.out.println("方块形状:" + getShape() + ",颜色:" + color); } }
-
I图形类(具体享元角色)
public class Ibox extends AbstractBox{ public String getShape(){ return "I"; } }
-
L图形类(具体享元角色)
public class Lbox extends AbstractBox{ public String getShape(){ return "L"; } }
-
O图形类(具体享元角色)
public class Obox extends AbstractBox{ public String getShape(){ return "O"; } }
-
工厂类,将该类设计为单例
public class BoxFactory{ private HashMap<String,AbstractBox> map; //在构造方法中进行初始化操作 private BoxFactory(){ map = new HashMap<String,AbstractBox>(); map.put("I",new Ibox()); map.put("L",new Lbox()); map.put("O",new Obox()); } private static BoxFactory factory = new BoxFactory(); //提供一个方法获取该工厂类对象 public static BoxFactory getInstance(){ return factory; } //根据名称获取图形对象 public AbstractBox getShape(String name){ return map.get(name); } }
-
客户端类(Client)
public class Client{ public static void main(String[] args){ //获取I图形对象 AbstractBox box1 = BoxFactory.getInstance().getShape("I"); box1.display("灰色"); //获取L图形对象 AbstractBox box2 = BoxFactory.getInstance().getShape("L"); box2.display("绿色"); //获取O图形对象 AbstractBox box3 = BoxFactory.getInstance().getShape("O"); box3.display("蓝色"); //获取O图形对象 AbstractBox box4 = BoxFactory.getInstance().getShape("O"); box4.display("红色"); System.out.println("两次获取到的O图形对象是否是同一个对象:" + (box3 == box4)); } }
-