愿你如阳光,明媚不忧伤。
目録
5. 外观模式
Facade 又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
外观模式优点和缺点
- 优点
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
- 缺点
- 不能很好地限制客户使用子系统类,很容易带来未知风险。
- 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观模式的应用场景
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
外观模式的结构
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。1. 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
2. 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
3. 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
外观模式的实现
package com.example.demo.controller;
public class FacadeController {
public static void main(String[] args) {
Facade facade = new Facade();
facade.method();
}
}
// 外观角色
class Facade {
private SubSystem1 obj1 = new SubSystem1();
private SubSystem2 obj2 = new SubSystem2();
private SubSystem3 obj3 = new SubSystem3();
public void method() {
obj1.method1();
obj2.method2();
obj3.method3();
}
}
// 子系统角色1
class SubSystem1 {
public void method1() {
System.out.println("子系统1的method1()被调用");
}
}
// 子系统角色2
class SubSystem2 {
public void method2() {
System.out.println("子系统2的method2()被调用");
}
}
// 子系统角色3
class SubSystem3 {
public void method3() {
System.out.println("子系统3的method3()被调用");
}
}
-----------------------------------------------------------------
・【运行结果】
子系统1的method1()被调用
子系统2的method2()被调用
子系统3的method3()被调用
外观模式的扩展
在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题。
- 抽象外观模式
6. 享元模式
Flyweight 也叫蝇量模式,常用于系统底层开发,运用共享技术来有效地支持大量细粒度对象的复用(例如围棋和五子棋中的黑白棋子)。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。享元模式其实是工厂方法模式的一个改进机制,同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。享元模式的本质是缓存共享对象,降低内存消耗。String常量池,数据库连接池,缓冲池等都是享元模式的应用。
享元模式优点和缺点
- 优点
- 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
- 缺点
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
- 读取享元模式的外部状态会使得运行时间稍微变长。
享元模式的应用场景
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
- 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
- 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
享元模式的结构
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态:I. 内部状态指对象共享出来的信息,存储在享元信息内部,并且不会随环境的改变而改变;
II. 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。
1. 抽象享元(Flyweight)角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
4. 享元工厂(Flyweight Factory)角色:构建一个池容器,负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
享元模式的实现
package com.example.demo.controller;
import java.util.HashMap;
public class FlyweightController {
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。"));
}
}
// 非享元角色
class UnsharedConcreteFlyweight {
private String info;
UnsharedConcreteFlyweight(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
// 抽象享元角色
interface Flyweight {
public void operation(UnsharedConcreteFlyweight state);
}
// 具体享元角色
class ConcreteFlyweight implements Flyweight {
private String key;
ConcreteFlyweight(String key) {
this.key = key;
System.out.println("具体享元" + key + "被创建!");
}
public void operation(UnsharedConcreteFlyweight outState) {
System.out.print("具体享元" + key + "被调用,");
System.out.println("非享元信息是:" + outState.getInfo());
}
}
// 享元工厂角色
class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight = (Flyweight) flyweights.get(key);
if (flyweight != null) {
System.out.println("具体享元" + key + "已经存在,被成功获取!");
} else {
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
-----------------------------------------------------------------
・【运行结果】
具体享元a被创建!
具体享元a已经存在,被成功获取!
具体享元a已经存在,被成功获取!
具体享元b被创建!
具体享元b已经存在,被成功获取!
具体享元a被调用,非享元信息是:第1次调用a。
具体享元a被调用,非享元信息是:第2次调用a。
具体享元a被调用,非享元信息是:第3次调用a。
具体享元b被调用,非享元信息是:第1次调用b。
具体享元b被调用,非享元信息是:第2次调用b。
享元模式的扩展
享元模式中包含可以共享的部分和不可以共享的部分。在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式和复合享元模式
- 单纯享元模式
种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类。
- 复合享元模式
这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享。
7. 组合模式
Composite 也叫作整体-部分模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。
组合模式优点和缺点
- 优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
- 缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 要求较高的抽象性,如果节点和叶子有很多差异性(属性和方法)的话,不适合使用组合模式;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
组合模式的应用场景
- 在需要表示一个对象整体与部分的层次结构的场合。
- 简化客户端操作,要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
组合模式的结构
1. 抽象构件(Component)角色:它的主要作用是为树枝构件和树叶构件声明公共接口,并实现它们的默认行为。2. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
3. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
- 透明式组合模式
在透明式的组合模式中抽象构件声明访问和管理子类的接口。由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
透明式组合模式的实现
package com.example.demo.controller;
import java.util.ArrayList;
public class CompositeController {
public static void main(String[] args) {
AbstractComponent ac0 = new Composite();
AbstractComponent ac1 = new Composite();
AbstractComponent leaf1 = new Leaf("1");
AbstractComponent leaf2 = new Leaf("2");
AbstractComponent leaf3 = new Leaf("3");
ac0.add(leaf1);
ac0.add(ac1);
ac1.add(leaf2);
ac1.add(leaf3);
ac0.operation();
ac0.getChild(2).operation(); ;
}
}
// 抽象构件
interface AbstractComponent {
public void add(AbstractComponent ac);
public void remove(AbstractComponent ac);
public AbstractComponent getChild(int i);
public void operation();
}
// 树枝构件
class Composite implements AbstractComponent {
private ArrayList<AbstractComponent> children = new ArrayList<AbstractComponent>();
public void add(AbstractComponent ac) {
children.add(ac);
}
public void remove(AbstractComponent ac) {
children.remove(ac);
}
public AbstractComponent getChild(int i) {
return children.get(i);
}
public void operation() {
for (Object obj : children) {
((AbstractComponent) obj).operation();
}
}
}
// 树叶构件
class Leaf implements AbstractComponent {
private String name;
public Leaf(String name) {
this.name = name;
}
public void add(AbstractComponent ac) {
}
public void remove(AbstractComponent ac) {
}
public AbstractComponent getChild(int i) {
return null;
}
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
-----------------------------------------------------------------
・【运行结果】
树叶1:被访问!
树叶2:被访问!
树叶3:被访问!
树叶2:被访问!
树叶3:被访问!
- 安全式组合模式
在安全式的组合模式中,不声明访问和管理子类的接口,管理工作由树枝构件完成(总的抽象类或接口,定义一些通用的方法,比如新增、删除)。这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
安全式组合模式的实现
package com.example.demo.controller;
import java.util.ArrayList;
public class CompositeController {
public static void main(String[] args) {
Composite ac0 = new Composite();
Composite ac1 = new Composite();
AbstractComponent leaf1 = new Leaf("1");
AbstractComponent leaf2 = new Leaf("2");
AbstractComponent leaf3 = new Leaf("3");
ac0.add(leaf1);
ac0.add(ac1);
ac1.add(leaf2);
ac1.add(leaf3);
ac0.operation();
// 由于根节点参照变为枝节点,所以下标要做对应的修改
ac0.getChild(1).operation();
;
}
}
// 抽象构件
interface AbstractComponent {
// 只保留公共方法,注释其余方法
// public void add(AbstractComponent ac);
//
// public void remove(AbstractComponent ac);
//
// public AbstractComponent getChild(int i);
public void operation();
}
// 树枝构件
class Composite implements AbstractComponent {
private ArrayList<AbstractComponent> children = new ArrayList<AbstractComponent>();
public void add(AbstractComponent ac) {
children.add(ac);
}
public void remove(AbstractComponent ac) {
children.remove(ac);
}
public AbstractComponent getChild(int i) {
return children.get(i);
}
public void operation() {
for (Object obj : children) {
((AbstractComponent) obj).operation();
}
}
}
// 树叶构件,只实现公共方法
class Leaf implements AbstractComponent {
private String name;
public Leaf(String name) {
this.name = name;
}
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
-----------------------------------------------------------------
・【运行结果】
树叶1:被访问!
树叶2:被访问!
树叶3:被访问!
树叶2:被访问!
树叶3:被访问!
组合模式的扩展
如果对前面介绍的组合模式中的树叶节点和树枝节点进行抽象,也就是说树叶节点和树枝节点还有子节点,这时组合模式就扩展成复杂的组合模式了
- 抽象组合模式
【每日一面】
说说聚合与组合的关系?
composition:表示类之间整体和部分的关系,“contains-a”关系,组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在。部分对象与整体对象之间具有共生死的关系。
aggregation:表示整体与部分的关系,“has-a”关系,聚合关系表示整体与部分的关系比较弱,代表部分事物的对象与代表聚合事物的对象的生存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。
组合中的各个部分生命周期相同,整体与部分关系强(人和大脑);聚合中的各个部分与彼此的生命周期无关,整体与部分关系较弱(人和电脑)。