装饰模式
接口 Component
其实已经有了 ConcreteComponentA
和 ConcreteComponentB
两个实现类了,但是,如果我们要增强这两个实现类的话,我们就可以采用装饰模式,用具体的装饰器来装饰实现类,以达到增强的目的。
所有的具体装饰者们 ConcreteDecorator* 都可以作为 Component 来使用,因为它们都实现了 Component 中的所有接口。它们和 Component 实现类 ConcreteComponent* 的区别是,它们只是装饰者,起装饰作用,都只是在具体的实现中加了层皮来装饰而已。
例子
饮料分为三类:红茶、绿茶、咖啡,在这三大类的基础上,又增加了许多的口味,什么金桔柠檬红茶、金桔柠檬珍珠绿茶、芒果红茶、芒果绿茶等等,仔细看下,其实原料也没几样,但是可以搭配出很多组合。
红茶、绿茶、咖啡是最基础的饮料,其他的像金桔柠檬、芒果、珍珠、椰果、焦糖等都属于装饰用的。当然,在开发中,我们确实可以像门店一样,开发这些类:LemonBlackTea、LemonGreenTea、MangoBlackTea、MangoLemonGreenTea......但是,这样子干肯定是不行的,这会导致我们需要组合出所有的可能,而且如果需要在红茶中加双份柠檬怎么办?
// 饮料抽象基类
public abstract class Beverage {
// 返回描述
public abstract String getDescription();
// 返回价格
public abstract double cost();
}
// 定义基础饮料实现类,红茶、绿茶和咖啡
public class BlackTea extends Beverage {
public String getDescription() {
return "红茶";
}
public double cost() {
return 10;
}
}
public class GreenTea extends Beverage {
public String getDescription() {
return "绿茶";
}
public double cost() {
return 11;
}
}
public class CoffeeTea extends Beverage {
public String getDescription() {
return "咖啡";
}
public double cost() {
return 12;
}
}
// 调料 也就是装饰者的基类,此类必须继承自 Beverage
public abstract class Condiment extends Beverage {
}
// 定义柠檬、芒果等具体的调料,它们属于装饰者,毫无疑问,这些调料肯定都需要继承调料 Condiment 类
public class Lemon extends Condiment {
private Beverage bevarage;
// 这里很关键,需要传入具体的饮料,如需要传入没有被装饰的红茶或绿茶,
// 当然也可以传入已经装饰好的芒果绿茶,这样可以做芒果柠檬绿茶
public Lemon(Beverage bevarage) {
this.bevarage = bevarage;
}
public String getDescription() {
// 装饰
return bevarage.getDescription() + ", 加柠檬";
}
public double cost() {
// 装饰
return beverage.cost() + 2; // 加柠檬需要 2 元
}
}
public class Mango extends Condiment {
private Beverage bevarage;
public Mango(Beverage bevarage) {
this.bevarage = bevarage;
}
public String getDescription() {
return bevarage.getDescription() + ", 加芒果";
}
public double cost() {
return beverage.cost() + 3; // 加芒果需要 3 元
}
}
// 给每一种调料都加一个类
客户端调用
public static void main(String[] args) {
// 首先,需要一个基础饮料,红茶、绿茶或咖啡
Beverage beverage = new GreenTea();
// 开始装饰
beverage = new Lemon(beverage); // 先加一份柠檬
beverage = new Mongo(beverage); // 再加一份芒果
System.out.println(beverage.getDescription() + " 价格:¥" + beverage.cost());
//"绿茶, 加柠檬, 加芒果 价格:¥16"
}
// 如果需要 芒果-珍珠-双份柠檬-红茶:
Beverage beverage = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));
java IO 中的装饰模式。 InputStream 派生出来的部分类
InputStream 代表了输入流,具体的输入来源可以是文件(FileInputStream)、管道(PipedInputStream)、数组(ByteArrayInputStream)等,属于基础输入流。
FilterInputStream 承接了装饰模式的关键节点,它的实现类是一系列装饰器,比如 BufferedInputStream 代表用缓冲来装饰,也就使得输入流具有了缓冲的功能,LineNumberInputStream 代表用行号来装饰,在操作的时候就可以取得行号了,DataInputStream 的装饰,使得我们可以从输入流转换为 java 中的基本类型值。
门面模式
定义一个接口
public interface Shape {
void draw();
}
定义实现类
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Circle::draw()");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
客户端调用
public static void main(String[] args) {
// 画一个圆形
Shape circle = new Circle();
circle.draw();
// 画一个长方形
Shape rectangle = new Rectangle();
rectangle.draw();
}
以上是经常写的代码,需要画圆就要先实例化圆,画长方形就需要先实例化一个长方形,然后再调用相应的 draw() 方法。
使用门面模式:
定义一个门面
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
/**
* 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定
*/
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
客户端调用
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();
// 客户端调用现在更加清晰了
shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
门面模式的优点显而易见,客户端不再需要关注实例化时应该使用哪个实现类,直接调用门面提供的方法就可以了,因为门面类提供的方法的方法名对于客户端来说已经很友好了。
组合模式
组合模式用于表示具有层次结构的数据,使得我们对单个对象和组合对象的访问具有一致性。
例子:每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。
public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates; // 下属
public Employee(String name,String dept, int sal) {
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList<Employee>();
}
public void add(Employee e) {
subordinates.add(e);
}
public void remove(Employee e) {
subordinates.remove(e);
}
public List<Employee> getSubordinates(){
return subordinates;
}
public String toString(){
return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
}
}
通常,这种类需要定义 add(node)、remove(node)、getChildren() 这些方法。
享元模式
复用已经生成的对象
复用对象最简单的方式是,用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候,先到 HashMap 中看看有没有,如果没有,再生成新的对象,然后将这个对象放入 HashMap 中。
结构型模式总结
代理模式是做方法增强的
适配器模式是把鸡包装成鸭这种用来适配接口的
桥梁模式做到了很好的解耦
装饰模式从名字上就看得出来,适合于装饰类或者说是增强类的场景
门面模式的优点是客户端不需要关心实例化过程,只要调用需要的方法即可
组合模式用于描述具有层次结构的数据
享元模式是为了在特定的场景中缓存已经创建的对象,用于提高性能