结构型设计模式
2.结构型模式
2.1适配器模式
-
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
-
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
-
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
-
如何解决:继承或依赖(推荐)。
-
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
-
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
-
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
-
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
类适配器模式
- 用泛化关系
package adapterpattern;
// 目标接口:当前系统业务所期待的接口,它可以是抽象类或接口。
interface Target {
void request();
}
// 适配者接口:它是被访问和适配的现存组件库中的组件接口。
class Adaptee {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}
// 类适配器类::它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
class ClassAdapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}
// 客户端代码
public class AdapterTest {
public static void main(String[] args) {
Target target = new ClassAdapter();
target.request();
}
}
对象适配器模式
- 用组合关系
//对象适配器类
class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
//客户端代码
class ObjectAdapterTest {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}
}
2.2桥接模式
-
意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
-
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
-
何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。
-
如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
-
关键代码:抽象类依赖实现类。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
-
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
-
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
-
注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。
// 品牌
public interface Brand {
void info();
}
public class Lenovo implements Brand{
@Override
public void info() {
System.out.println("联想");
}
}
public class Apple implements Brand {
@Override
public void info() {
System.out.println("苹果");
}
}
// 种类
package bridgepattern;
public abstract class Computer {
// 用组合而不是继承
protected Brand brand;
public Computer(Brand brand) {
this.brand = brand;
}
public abstract void info();
}
class Desktop extends Computer{
public void info() {
brand.info();
System.out.println("台式机");
}
public Desktop(Brand brand) {
super(brand);
}
}
class Laptop extends Computer{
public void info() {
brand.info();
System.out.println("笔记本");
}
public Laptop(Brand brand) {
super(brand);
}
}
public class Test {
public static void main(String[] args) {
// 苹果笔记本
Computer computer = new Laptop(new Apple());
computer.info();
// 联想台式机
Computer computer1 = new Desktop(new Lenovo());
computer1.info();
}
}
2.3代理模式
- 意图:为其他对象提供一种代理以控制对这个对象的访问。
- 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
- 何时使用:想在访问一个类时做一些控制。
- 如何解决:增加中间层。
- 关键代码:实现与被代理类组合。
- 优点: 1、职责清晰。 2、高扩展性。 3、智能化。
- 缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
- 注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
- 根据代理的创建时期,代理模式分为静态代理和动态代理。
- 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
- 动态:在程序运行时,运用反射机制动态创建而成。代理的是一个接口,可以代理多个类(实现同一个接口)。
静态代理
package proxypattern;
public class StaticProxy {
public static void main(String[] args) {
You you = new You();
WeddingCompany weddingCompany = new WeddingCompany(you);
weddingCompany.HappyMarry(); // 代理真实对象you
new Thread(()-> System.out.println("haha")).start();
}
}
interface Marry{
void HappyMarry();
}
// 真实对象
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("marry!");
}
}
// 代理对象
class WeddingCompany implements Marry{
private Marry target; // 要代理的对象
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
target.HappyMarry();
after();
}
public void before(){
System.out.println("准备工作");
}
public void after(){
System.out.println("事后工作");
}
}
动态代理
-
两大类的动态代理
- 基于接口:JDK动态代理
- 基于类:CGLIB动态代理
- java字节码实现:javasist
-
ProxyInvocationHandler.java
package proxypattern;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
// 使用反射机制
Object result = method.invoke(target, args);
after();
return result;
}
public void before(){
System.out.println("准备工作");
}
public void after(){
System.out.println("事后工作");
}
}
- Rent.java
public interface Rent {
void rent();
}
- Host.java
// 房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
- Client.java
public class Client {
public static void main(String[] args) {
// 真实角色
Host host = new Host();
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
// 通过调用程序处理角色来处理要调用的接口对象
proxyInvocationHandler.setTarget(host);
// 动态生成的代理角色
Rent proxy = (Rent) proxyInvocationHandler.getProxy();
proxy.rent();
}
}
2.4 装饰模式
-
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标。
-
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
-
何时使用:在不想增加很多子类的情况下扩展类。
-
如何解决:将具体功能职责划分,同时继承装饰者模式。
-
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
-
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
-
缺点:多层装饰比较复杂。
-
注意事项:可代替继承。
package decoratorpattern;
public class DecoratorPattern {
public static void main(String[] args) {
Component p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d = new ConcreteDecorator(p);
d.operation();
}
}
// 抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象。
interface Component {
void operation();
}
// 具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责。
class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
@Override
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
// 抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
// 新加的功能
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}
2.5享元模式
-
类图说明
- UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
- Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
- ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
- FlyweightFactory 是享元工厂角色,它是关键字 key 来管理具体享元;
- 客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。
-
意图:运用共享技术有效地支持大量细粒度的对象。
-
主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
-
何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。
-
如何解决:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
-
关键代码:用 HashMap 存储这些对象。享元模式的本质是缓存共享对象,降低内存消耗。
-
优点:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
-
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
-
注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
package flyweightpattern;
import java.util.HashMap;
public class FlyweightPattern {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight f01 = factory.getFlyweight("a");
Flyweight f02 = factory.getFlyweight("a");
Flyweight f03 = factory.getFlyweight("a");
Flyweight f11 = factory.getFlyweight("b");
Flyweight f12 = factory.getFlyweight("b");
f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
}
}
// 抽象享元角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口。
interface Flyweight {
// 非享元的外部状态以参数的形式通过方法传入
void operation(UnsharedConcreteFlyweight state);
}
// 具体享元角色:实现抽象享元角色中所规定的接口。
class ConcreteFlyweight implements Flyweight {
private String key;
ConcreteFlyweight(String key) {
this.key = key;
System.out.println("具体享元" + key + "被创建!");
}
@Override
public void operation(UnsharedConcreteFlyweight outState) {
System.out.print("具体享元" + key + "被调用,");
System.out.println("非享元信息是:" + outState.getInfo());
}
}
// 非享元角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
class UnsharedConcreteFlyweight {
// 非共享的外部状态信息
private String info;
UnsharedConcreteFlyweight(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
// 享元工厂角色:负责创建和管理享元角色。
class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<>();
// 用key管理具体享元
// 当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象
public Flyweight getFlyweight(String key) {
Flyweight flyweight = flyweights.get(key);
if (flyweight != null) {
// 如果存在则提供给客户
System.out.println("具体享元" + key + "已经存在,被成功获取!");
} else {
// 如果不存在的话,则创建一个新的享元对象
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
2.6外观模式
-
意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
-
主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。
-
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
-
如何解决:客户端不与系统耦合,外观类与系统耦合。
-
关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
-
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
-
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
-
注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。
package facadepattern;
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()被调用!");
}
}
2.7组合模式
-
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
-
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
-
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
-
如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
-
关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
-
优点: 1、高层模块调用简单。 2、节点自由增加。
-
缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
-
注意事项:定义时为具体类。
透明方式
- 在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
- 访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素
package compositepattern;
import java.util.ArrayList;
public class CompositePattern {
public static void main(String[] args) {
// 访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
// 抽象构件:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。
// 在透明式的组合模式中抽象构件还声明访问和管理子类的接口;
// 在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
interface Component {
void add(Component c);
void remove(Component c);
Component getChild(int i);
void operation();
}
// 树叶构件:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
// 树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常)
@Override
public void add(Component c) {
}
@Override
public void remove(Component c) {
}
@Override
public Component getChild(int i) {
return null;
}
@Override
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
// 树枝构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。
// 它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<>();
@Override
public void add(Component c) {
children.add(c);
}
@Override
public void remove(Component c) {
children.remove(c);
}
@Override
public Component getChild(int i) {
return children.get(i);
}
@Override
public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}
安全方式
在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
package compositepattern;
import java.util.ArrayList;
public class CompositePattern {
public static void main(String[] args) {
// 访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素
Composite c0 = new Composite();
Composite c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
// 抽象构件
interface Component {
void operation();
}
// 树叶构件
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
// 树枝构件
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<>();
public void add(Component c) {
children.add(c);
}
public void remove(Component c) {
children.remove(c);
}
public Component getChild(int i) {
return children.get(i);
}
@Override
public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}