网站建设为什么需要数据库,wordpress国内最好的主题,snippets wordpress,网站建设洽谈方案装饰器模式
以生活中的场景来举例#xff0c;一个蛋糕胚#xff0c;给它涂上奶油就变成了奶油蛋糕#xff0c;再加上巧克力和草莓#xff0c;它就变成了巧克力草莓蛋糕。
像这样在不改变原有对象的基础之上#xff0c;将功能附加到原始对象上的设计模式就称为装饰模式(D…装饰器模式
以生活中的场景来举例一个蛋糕胚给它涂上奶油就变成了奶油蛋糕再加上巧克力和草莓它就变成了巧克力草莓蛋糕。
像这样在不改变原有对象的基础之上将功能附加到原始对象上的设计模式就称为装饰模式(Decorator模式)属于结构型模式。
装饰器模式中主要有四个角色: Component : 定义被装饰对象的接口装饰器也需要实现一样的接口 ConcreteComponent: 具体的装饰对象,实现了Component接口,通常就是被装饰的原始对象 Decorator: 所有装饰器的抽象父类,需要定义与Component一致的接口并且持有一个被装饰的Component对象 ConcreteDecorator: 具体装饰器对象
代码示例
假设我们需要设计一个奖金的系统,目前一个工作人员的奖金包含三部分:
本月销售额的奖金 : 当月销售额的3%
累计销售额的奖金 : 累计销售额的0.1%
团队销售额的奖金 : 团队销售额的1%只有经理才有
使用装饰模式的实现如下 定义一个所有奖金的抽象接口然后定义一个BasicPrize的初始奖金对象不同的人奖金的组成不同就为其添加不同的装饰器 public abstract class Component {abstract double calPrize(String userName);
}
public class BasicPrize extends Component {//保存了一个员工与销售额的对应关系的mappublic static MapString,Double saleMoney new HashMapString,Double();static {saleMoney.put(小明,9000.0);saleMoney.put(小陈,20000.0);saleMoney.put(小王,30000.0);saleMoney.put(张经理,55000.0);}public double calPrize(String userName) {return 0;}
}
/**** 所有装饰器的抽象父类,持有一个被装饰对象*/
public abstract class Decorator extends Component {protected Component component;public Decorator(Component component){this.component component;}public abstract double calPrize(String userName);
}
/*** * 当月奖金计算规则*/
public class MonthPrizeDecorator extends Decorator{public MonthPrizeDecorator(Component component) {super(component);}public double calPrize(String userName) {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money this.component.calPrize(userName);//计算本奖金 对应员工的业务额的3%double prize BasicPrize.saleMoney.get(userName)*0.03;System.out.println(userName当月 业务奖金:prize);return money prize;}}/*** 累计奖金*/
public class SumPrizeDecorator extends Decorator {public SumPrizeDecorator(Component component) {super(component);}public double calPrize(String userName) {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money this.component.calPrize(userName);//计算本奖金 累计业务额的0.1% 假设是100000double prize 100000*0.001;System.out.println(userName当月 累计奖金:prize);return money prize;}}/*** 团队奖金*/
public class GroupPrizeDecorator extends Decorator {public GroupPrizeDecorator(Component component) {super(component);}public double calPrize(String userName) {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money this.component.calPrize(userName);//计算本奖金 本团队的业务额的1%double sumSale 0;for(double sale: BasicPrize.saleMoney.values()){sumSale sale;}double prize sumSale*0.01;System.out.println(userName当月 团队奖金:prize);return money prize;}}/*** Description*/
public class Client {public static void main(String[] args) throws FileNotFoundException {//基础对象 奖金0Component basePrice new BasicPrize();//当月奖金Component salePrize new MonthPrizeDecorator(basePrice);//累计奖金Component sumPrize new SumPrizeDecorator(salePrize);//团队奖金Component groupPrize new GroupPrizeDecorator(sumPrize);//普通员工的奖金由两部分组成double d1 sumPrize.calPrize(小明);System.out.println(小明总奖金:d1);double d2 sumPrize.calPrize(小陈);System.out.println(小陈总奖金:d2);double d3 sumPrize.calPrize(小王);System.out.println(小王总奖金:d3);//王经理的奖金由三部分组成double d4 groupPrize.calPrize(张经理);System.out.println(张经理总奖金:d4);}
}输出结果如下: 这里我们使用装饰器模式主要是为了方便组合和复用。在一个继承的体系中子类往往是互斥的比方在一个奶茶店它会有丝袜奶茶红茶果茶等用户想要一杯饮料一般都会在这些种类中选一种,不能一杯饮料既是果茶又是奶茶。然后用户可以根据自己的喜好添加任何想要的decorators,珍珠椰果布丁等这些添加物对所有茶类饮品都是相互兼容的并且是可以被允许反复添加的(同样的装饰器是否允许在同一个对象上装饰多次视情况而定像上面的奖金场景显然是不被允许的)
jdk中的装饰器模式
java.io包是用于输入输出的包这里使用了大量的装饰器模式我们再来体会一下装饰器模式的优点。
下图是jdk 输出流的一部分类图很明显是一个装饰模式的类图OutputStream是顶层父类FileOutputStream和ObjectOutputStream是具体的被装饰类FilterOutputStream是所有输出流装饰器的抽象父类 使用的代码如下: InputStream in new FileInputStream(/user/wangzheng/test.txt);InputStream bin new BufferedInputStream(in);byte[] data new byte[128];while (bin.read(data) ! -1) {//...}为什么Java IO设计的时候要使用装饰器模式, 而J不设计⼀个继承 FileInputStream 并且⽀持缓存的 BufferedFileInputStream 类呢?
如果InputStream 只有⼀个⼦类 FileInputStream 的话那我们在 FileInputStream 基础之上再设计⼀个孙⼦类BufferedFileInputStream也是可以接受的。但实际上继承 InputStream 的⼦类有很多。我们需要给每⼀个 InputStream 的⼦类 再继续派⽣⽀持缓存读取的⼦类。 除此之外我们还需要对功能进⾏其他⽅⾯的增强⽐如下⾯的DataInputStream 类⽀持按照基本数据类型int、boolean、long 等来读取数据。这种情形下使用继承的方式的话类的继承结构变得⽆⽐复杂代码维护起来也比较费劲。
按照装饰器模式的结构我们可以继承FilterOutputStream实现自定义的装饰器,并且在使用的时候可以和jdk自带的装饰器对象任意组合。
我们可以实现一个简单的复制输出内容的OutputStream装饰器
public class DuplicateOutputStream2 extends FilterOutputStream {/*** Creates an output stream filter built on top of the specified* underlying output stream.** param out the underlying output stream to be assigned to* the field ttthis.out/tt for later use, or* codenull/code if this instance is to be* created without an underlying stream.*/public DuplicateOutputStream2(OutputStream out) {super(out);}//将所有的内容复制一份输出 ab 变成aabbpublic void write(int b) throws IOException {super.write(b);super.write(b);}
}
装饰器类是否可以直接实现Component父类?
以输出流为例如果直接继承OutputStream来实现自定义装饰器
public class DuplicateOutputStream extends OutputStream {private OutputStream os;public DuplicateOutputStream(OutputStream os){this.os os;}//将所有的内容复制一份输出 ab 变成aabbpublic void write(int b) throws IOException {os.write(b);os.write(b);}
}public class ClientTest {public static void main(String[] args) throws IOException {DataOutputStream dataOutputStream new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream2(new FileOutputStream(1.txt))));testOutputStream(dataOutputStream);DataOutputStream dataOutputStream1 new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream(new FileOutputStream(1.txt))));testOutputStream(dataOutputStream1);}public static void testOutputStream(DataOutputStream dataOutputStream) throws IOException {DataInputStream dataInputStream new DataInputStream(new FileInputStream(1.txt));dataOutputStream.write(bdsaq.getBytes());dataOutputStream.close();System.out.println(dataInputStream.available());byte[] bytes3 new byte[dataInputStream.available()];dataInputStream.read(bytes3);System.out.println(文件内容:new String(bytes3));}
}输出结果: 乍一看好像没什么区别但是如果把BufferedOutputStream装饰器和自定义的装饰器互换。
DataOutputStream dataOutputStream2 new DataOutputStream(new DuplicateOutputStream2(new BufferedOutputStream(new FileOutputStream(1.txt))));
testOutputStream(dataOutputStream2);DataOutputStream dataOutputStream3 new DataOutputStream(new DuplicateOutputStream(new BufferedOutputStream(new FileOutputStream(1.txt))));
testOutputStream(dataOutputStream3);输出结果: 使用了实现OutputStream的DuplicateOutputStream会出现没有正常输出数据这是因为我们使用了BufferedOutputStream这个带缓存区的输出流缓存区的输出流在缓存区没有满的情形下是不会进行输出操作的。一般情形下我们在调用jdk的DataOutputStream的close方法的时候会调用其传入的输出流的flush()方法,并且向下传递调用BufferedOutputStream里的数据会正常输出。
在使用DuplicateOutputStream2的时候其调用关系是这样的:
dataOutputStream.close()–duplicateOutputStream2.flush()–bufferedOutputStream.flush()
使用DuplicateOutputStream的时候由于DuplicateOutputStream继承的OutputStream的flush()方法是空实现所以不会继续往下调用bufferedOutputStream的flush()方法故而最后没有得到输出内容
所以装饰器类需要继承Decorator抽象父类而不是直接继承Component抽象类我认为是为了在Decorator里实现一些共性的代码以便在使用装饰器的时候能够更加自由无视其组合顺序 (当然如果你的Decorator里没有任何逻辑代码在合适的场景下你可以不定义抽象装饰器类)
总结
装饰器模式主要用于解决继承关系过于复杂的问题通过组合来替代继承。 它主要的作⽤是给原始类添加增强功能。
除此之外装饰器模式还有⼀个特点那就是可以对原始类嵌套使⽤多个装饰器。为了满⾜这个应⽤场景在设计的时候装饰器类需要跟原始类继承相同的抽象类或者接⼝。