1.适配器模式(Adapter)
2.桥接模式(Bridge)
3.组合模式(Composite)
4.装饰模式(Decorator)
5.外观模式(Facade)
6.享元模式(Flyweight)
7.代理模式(Proxy)
结构型关注点: 继承与组合,优先使用组合。(对象级别)
组合比继承更加灵活,可以随时替换组合中的对象。
适配器模式(adapter)
定义:将一个类的接口,转换成用户期望的另一个接口,适配器让原本不兼容的接口可以合作无间。
通俗来说:当前想要的接口没有,但是有的接口却不能用。我们把有但是不能用的东西适配成我们能用的东西然后给用户来调用。
角色:
- 目标抽象类(Target):定义用户所需要的接口,可以是一个接口或抽象类,也可以是一个具体的实现类。
- 适配器类(Adapter):将适配者类进行适配,并提供目标抽象类(Target)所需要的功能。
- 适配者类(Adaptee):即被适配的角色。包含了用户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
通过继承target,然后内部组合adaptee,实现适配器的功能。
优点:
1.将目标类与适配者解耦,通过适配器重用先用的代码,无需修改原有结构。
2.增加了类的透明性和复用性,同一个被适配的对象可以在多个不同系统中复用。
3.灵活性和扩展性都非常好,通过配置(配置文件,注入)可以很方便的更换适配器,可以在不修改原油代码的基础上增加新的适配器类,完全符合开闭原则。
不符合里式替换原则,子类覆盖了父类的方法。
应用场景:
1.系统需要使用一些现有的类,而这些类不符合系统要求。甚至没有源代码 (例如:数据库、缓存的驱动 或者JDK中native方法)
2.想创建一个重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作。
桥接模式(Bridge)
我们都去买过手机,手机按照品牌分可以分为华为、小米、oppo、vivo等品牌,如果这些手机按照内存分又可以分为4G、6G、8G等等。假如我们每一种手机都想要玩一下,至少需要4*3个。这对我们来说这些手机也太多了,竟然有12个,最主要的是手机品牌和内存是放在一起的。现在有这样一种机制,手机牌品商是一个公司,做手机内存的是一个公司,想要做什么手机我们只需要让其两者搭配起来即可。这就是桥接模式。
定义:将抽象部分与它的实现部分分离,使他们都可以独立的变化。
如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”
角色:
- 1.抽象角色(Abstraction):抽象的定义,并保存一个Implementor的引用。指的是手机抽象类。
- 2.具体角色(RefineAbstraction):指的是具体手机品牌
- 3.抽象实现角色(Implementor):定义实现类的接口,提供基本操作,其实现交给子类。
-
- 具体实现角色(ConcreteImplementor):抽象实现角色的具体实现。在程序运行时,子类对象将替换其父类对象,提供给Abstraction具体的业务操作方法
通俗来说:两个抽象角色,两个具体角色,其中一个抽象角色A中组合了另外一个抽象角色B,抽象角色A的具体实现是在其实现类中。这样就将抽象部分和具体部分离。 就是组合模式的应用。是里式替换原则与单一职责最好的体现。
优点:
- 1.分离抽象和实现部分:把手机、内存抽象出来实现与之分离。
- 2.松耦合 多个维度分开
- 3.单一职责,每个维度各干各的活
为什么使用桥接模式而不是继承呢?
继承是一种强耦合关系,父类的任何变化都会导致子类发生变化。桥接模式各维度变化不会相互影响。
应用场景:
- 1.一个类存在多个独立变化的维度,且多个维度都需要进行扩展(进行变化)。
桥接模式和适配器模式的区别
桥接模式和适配器模式都是通过组合来完成的。有一定的相似度。
1.都是把两个对象组合起来配合工作。桥接模式的目标是分离。适配器模式的目标是合并。
2.适配器是为了让两个已有的对象联合起来让他们工作。先有两个角色,而后才出现的适配器。
桥接模式是从设计阶段整体设计方案,设计初期出现。
装饰模式(Decorator)
定义:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更佳灵活。
通俗来说:就是对一个类或者方法进行包装,增强它的功能。spring源码中大量的wapper 就是装饰模式。
角色:
- 抽象组件(Component ):是一个接口或一个抽象类,就是定义最核心的角色,也就是原始的对象,在装饰模式中,必然有一个最基本、最核心的接口或抽象类来充当Component角色。
- 具体组件(ConcreteComponent ):抽象组件(Component )的实现类,具体要装饰的角色。
- 抽象装饰器(Decorator ): 一般是一个抽象类, 实现接口或抽象方法, 它里面不一定有抽象的方法, 在它的属性中必然有一个private变量指向抽象组件(Component )。
- 具体装饰器(ConcreateDecrator):具体的装饰类,当只有一个装饰类时, 可以没有抽象装饰角色。
优点:
装饰模式是继承的一个替代方案,不管装饰了多少层,返回的对象还是Compont,组合代替继承的最优体现。比继承耦合性要低。
装饰模式可以动态的扩展一个实现类的功能。
应用场景:
inputStream,outPutStream等流
Spring源码中大量的wapper
代理模式(Proxy)
定义:是为其他对象提供一种,代理以控制对这个对象的访问。
通俗来说:就是不想让调用者直接操作(或调用者根本操作不到例如RPC),用代理对象操作完成。表现出调用者直接操作的效果。是对第三方对象的控制。
角色:
1.抽象主题角色(Subject):可以是抽象类,也可以是接口,就是被代理对象的接口。
2.具体主题角色(RealSubject):也叫被代理角色,具体业务逻辑的执行者。
3.代理角色(proxy):负责限制被代理角色,以及预处理和善后处理。
抽象主题角色:
public interface Subject {
/**
* 接口方法
*/
public void request();
}
具体主题角色:
public class ConcreteSubject implements Subject {
/**
* 具体的业务逻辑实现
*/
@Override
public void request() {
//业务处理逻辑
}
}
代理角色:
public class Proxy implements Subject {
/**
* 要代理的实现类
*/
private Subject subject = null;
/**
* 默认代理自己
*/
public Proxy() {
this.subject = new Proxy();
}
public Proxy(Subject subject) {
this.subject = subject;
}
/**
* 构造函数,传递委托者
*
* @param objects 委托者
*/
public Proxy(Object... objects) {
}
/**
* 实现接口方法
*/
@Override
public void request() {
this.before();
this.subject.request();
this.after();
}
/**
* 预处理
*/
private void before() {
//do something
}
/**
* 后处理
*/
private void after() {
//do something
}
}
调用方:
public class Client {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Proxy proxy = new Proxy(subject);
proxy.request();
}
}
代理分为:
1.静态代理:编译时确定。
-
2.动态代理:又叫做JDK代理,也叫接口代理,运行时确定代理类型。动态代理,代理对象是不需要实现代理接口的。通过JDK包中java.lang.reflect.Proxy 类中只需要使用newProxyInstance方法,传入三个参数完成代理。
- 1.ClassLoader loader 参数:指定当前目标对象使用的类加载器,获取加载器的方法是固定的。
- 2.Class> interfaces :目标对象实现的接口类型,使用泛型的方法确认类型。
-
3.InvocationHandler handler: 事件处理,执行目标对象方法时,会触发处理器的方法。
代理对象查看class是以$符号开头的,表示内存中动态生成的代理对象。
- 3.cglib代理:可以在内存中动态的创建对象(代理),而不需要目标类实现接口。前两者都需要目标类实现接口。cglib代理属于动态代理的范畴。
cglib代理也叫作子类代理。
cglib是一个强大的高性能的代码包,有4个jar 可以在运行期扩展java类与实现java接口,广泛被很多AOP的框架使用,例如Spring AOP。
什么时候使用JDK代理,什么时候使用cglib代理。
目标对象需要实现接口,用JDK代理。
目标对象不需要实现接口,用cglib代理。
cglib底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
cglib代理在内存中动态构建子类,注意代理的类不能为final,否则会报IllegalArgumentException。
cglib代理目标对象的方法,如果为final或static 那么就不会拦截(cglib代理主要通过拦截器实现的),即不会执行目标对象额外的业务方法。
具体实现
生成代理对象步骤
实现MethodIntercept接口的intercept()方法
优点:
1.职责清晰:不需要调用方知道代理对象内部逻辑,使用起来和原对象一样方便。特征是代理类和委托类实现相同接口。
2.高扩展性:
被代理类随时发生变化,但是只要接口不变,代理类就可以不用做任何修改。而且也可以增加额外的功能。
应用场景:
- 1远程代理:也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在不同地址空间的事实。
- 2虚拟代理:是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。
- 3安全代理:用来控制真实对象访问时的权限。
装饰模式和代理模式的区别
装饰模式:以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
代理模式:给对象提供一个代理,并由代理对象来控制对象的引用。
装饰模式视为对象透明地增强功能,代理模式是对代理对象加以控制。
- 1.但装饰模式对客户端是透明的。代理模式对客户端是封装的。
- 2.装饰模式是对第三方的增强,代理模式是对第三方的限制。
组合模式(Composite)
定义:有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性,这种类型的设计模式属于结构型模式。
通俗来说:就是一个对象内部包含了这个对象本身的一个或多个对象。其实就是一个树形结构转成java类的映射。
优点:
组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码。
缺点:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
应用场景:
JDK1.8之前,HashMap底层利用数组和链表实现,链表里面存储的对象就是Node对象,node对象正式我们文章的叶子,Node实现了我们Entry接口,node里面有node,节点里面有节点,Node 是个非常经典的组合模式。
组合模式比较简单,不再赘述。
外观模式(Facade)
定义:是哟中通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式由一个统一的接口,外部应用不关心内部子系统的具体实现。
外观模式是迪米特法则的典型应用。
通俗来说:将多个子系统的调用,由外观模式统一去调用,而客户只需要调用这个外观实现类。
角色:
- 1.外观角色:为多个子系统提供一个共同的接口。
- 2.子系统角色:实现系统的部分功能,用户可以通过外观角色访问它。
外观模式实现代码:
package facade;
public class FacadePattern
{
public static void main(String[] args)
{
Facade f=new Facade();
f.method();
}
}
//外观角色
class Facade
{
private SubSystem01 obj1=new SubSystem01();
private SubSystem02 obj2=new SubSystem02();
private SubSystem03 obj3=new SubSystem03();
public void method()
{
obj1.method1();
obj2.method2();
obj3.method3();
}
}
//子系统角色
class SubSystem01
{
public void method1()
{
System.out.println("子系统01的method1()被调用!");
}
}
//子系统角色
class SubSystem02
{
public void method2()
{
System.out.println("子系统02的method2()被调用!");
}
}
//子系统角色
class SubSystem03
{
public void method3()
{
System.out.println("子系统03的method3()被调用!");
}
}
优点:
1.降低子系统与用户之间的耦合度,子系统变化不影响用户调用。
2.对客户屏蔽子系统组件,减少客户处理的对象数目,使用起来更加容易。
缺点:
新增的子系统可能需要修改外观类或用户的源代码,违背了开闭原则。
应用场景:
1.当子系统很多时,外观模式为系统设计一个简单的接口供外界访问
享元模式(Flyweight)
定义:用于减少创建对象的数量,以减少内存占用和提高性能,运用共享技术有效地支持大量细粒度的对象。
通俗来说:池技术就是享元模式,String常量池,数据库连接池等。一般和工厂模式结合。在创建对象时,如果有缓存的,就直接使用,避免大量创建模式。就是(工厂)+(缓存池)
也叫蝇量模式。
角色:
抽象享元角色(Flyweight):享元对象的抽象基类或接口,同时定义出内部状态和外部状态的接口或实现。
具体享元角色(ConcreteFlyweight):实现抽象享元角色。该角色的内部状态与环境无关,不能出现一个操作改变内部状态同时修改了外部状态。
享元工厂(FlyweightFactory):负责管理享元对象池和创建享元对象。
class Client {
public static void main(String[] args) {
IFlyweight flyweight1 = FlyweightFactory.getFlyweight("aa");
IFlyweight flyweight2 = FlyweightFactory.getFlyweight("bb");
flyweight1.operation("a");
flyweight2.operation("b");
}
// 抽象享元角色
interface IFlyweight {
void operation(String extrinsicState);
}
// 具体享元角色
static class ConcreteFlyweight implements IFlyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("Object address: " + System.identityHashCode(this));
System.out.println("IntrinsicState: " + this.intrinsicState);
System.out.println("ExtrinsicState: " + extrinsicState);
}
}
// 享元工厂
static class FlyweightFactory {
private static Map pool = new HashMap<>();
// 因为内部状态具备不变性,因此作为缓存的键
public static IFlyweight getFlyweight(String intrinsicState) {
if (!pool.containsKey(intrinsicState)) {
IFlyweight flyweight = new ConcreteFlyweight(intrinsicState);
pool.put(intrinsicState, flyweight);
}
return pool.get(intrinsicState);
}
}
}
优点:
大大减少了对象的创建,降低了程序内存的占用,提高效率。
缺点:
1.需要分离内部状态和外部状态。
2.提高了系统的复杂度。
应用场景:
1.需要缓冲池的场景
2.系统中存在大量相似对象
3.Integer 等包装类中 valueof() 返回的是同一个对象。IntegerCache中获取的数值 使用了享元模式。