一、一句话描述装饰者模式
装饰者模式: 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。
在装饰者模式中,会有一个Decorator的类,该类的作用类似于一个增强器,继承了该类的类,会被增加一些原本没有的功能/数据/方法。
二、先简要说明一下类的继承层次
现在有三个类:Drink(饮料类)、Coffee(咖啡类)、Decaf(低咖啡因咖啡),类的继承关系如下:
三个类的代码如下:
public abstract class Drink {
public String description = "";
private float price = 0f;
public void setDescription(String description)
{
this.description=description;
}
public String getDescription()
{
return description+"-"+this.getPrice();
}
public float getPrice()
{
return price;
}
public void setPrice(float price)
{
this.price=price;
}
public abstract float cost();
}
public class Coffee extends Drink {
@Override
public float cost() {
// TODO Auto-generated method stub
return super.getPrice();
}
}
public class Decaf extends Coffee {
public Decaf()
{
super.setDescription("Decaf");
super.setPrice(3.0f);
}
}
调用代码如下:
public static void main(String[] args) {
Drink order;
order=new Decaf();
System.out.println("order1 price:"+order.cost());
System.out.println("order1 desc:"+order.getDescription());
}
打印结果如下:
order1 price:3.0
order1 desc:Decaf-3.0
可见,当我们调用Drink的子类Decarf,用以初始化Drink类的时候,Decarf类–>Coffee 类–>Drink类,逐层向上super.set()了description和price两个属性,因而,当打印Drink类的这两个属性的时候,打印出来的是子类Decarf的属性,这种向上传递的机制就是装饰者模式得以运行的基础。
三、进一步的试验
我们再添加一个装饰者类Decorator,并且继续新增3个继承Coffee的子类,分别为 LongBlack,Espresso和ShortBlack;新增三个继承Decorator的子类,分别为Chocolate、Milk和Soy。
public class Decorator extends Drink {
private Drink Obj;
public Decorator(Drink Obj){
this.Obj=Obj;
}
@Override
public float cost() {
// TODO Auto-generated method stub
return super.getPrice()+Obj.cost();
}
@Override
public String getDescription()
{
return super.description+"-"+super.getPrice()+"&&"+Obj.getDescription();
}
}
public class LongBlack extends Coffee{
public LongBlack()
{
super.setDescription("LongBlack");
super.setPrice(6.0f);
}
}
public class Espresso extends Coffee{
public Espresso()
{
super.setDescription("Espresso");
super.setPrice(4.0f);
}
}
public class ShortBlack extends Coffee{
public ShortBlack()
{
super.setDescription("ShortBlack");
super.setPrice(5.0f);
}
}
public class Chocolate extends Decorator {
public Chocolate(Drink Obj) {
super(Obj);
// TODO Auto-generated constructor stub
super.setDescription("Chocolate");
super.setPrice(3.0f);
}
}
public class Milk extends Decorator {
public Milk(Drink Obj) {
super(Obj);
// TODO Auto-generated constructor stub
super.setDescription("Milk");
super.setPrice(2.0f);
}
}
public class Soy extends Decorator {
public Soy(Drink Obj) {
super(Obj);
// TODO Auto-generated constructor stub
super.setDescription("Soy");
super.setPrice(1.5f);
}
}
再测试一下:
public static void main(String[] args) {
Drink order;
order=new Decaf();
System.out.println("****************");
order=new LongBlack();
order=new Milk(order);
order=new Chocolate(order);
order=new Chocolate(order);
System.out.println("order2 price:"+order.cost());
System.out.println("order2 desc:"+order.getDescription());
}
结果如下:
****************
order2 price:14.0
order2 desc:Chocolate-3.0&&Chocolate-3.0&&Milk-2.0&&LongBlack-6.0
当前的类图如下:
通过分析Decorator的代码可知,实际上,Decorator中有ShortBlack、Decarf、LongBlack三个类的公共超类Coffee的父类Drink的私有化属性,相当于,在每次使用Drink的子类来初始化Drink类的时候,继承了Decorator类的实体类都会通过Decortor的set方法对超类Drink的值进行重新设值,从而实现了功能的叠加和增强。
在这个层面,Decorator类似于一个拦截器和发送器的组合,每次都会把子类的变化接收进来进行编辑,然后会把编辑后的成果以重写父类set方法的形式,传递给父类。
或者说,这“就相当于生活当中制作三明治的过程,我们先加一根香肠,再香肠上抹上一层奶油,再加上几片蔬菜,最后拿面包夹上。
”
四、Java的内置装饰者模式的运用:
功能类UpperCaseInputStream 和测试类InputTest分别如下:
public class UpperCaseInputStream extends FilterInputStream{
protected UpperCaseInputStream(InputStream in) {
super(in);
// TODO Auto-generated constructor stub
}
@Override
public int read() throws IOException
{
int c=super.read();
return c==-1?c:Character.toUpperCase((char)(c));
}
@Override
public int read(byte[] b,int offset,int len) throws IOException
{
int result=super.read(b,offset,len);
for(int i=0;i<result;i++)
{
b[i]=(byte)Character.toUpperCase((char)(b[i]));
}
return result;
}
}
public class InputTest {
public static void main(String[] args) {
int c;
try {
InputStream in = new UpperCaseInputStream(new BufferedInputStream(
new FileInputStream("C:\\Users\\my\\Desktop\\test.txt")));
while((c=in.read())>=0)
{
System.out.print((char)c);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
test文件夹内容:
ASEHUDsehjehuerhue231
测试输出内容:
ASEHUDSEHJEHUERHUE231
这里值得注意的是InputStream 类:
public
class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected FilterInputStream(InputStream in) {
this.in = in;
}
/**
* Reads the next byte of data from this input stream. The value
* byte is returned as an <code>int</code> in the range
* <code>0</code> to <code>255</code>. If no byte is available
* because the end of the stream has been reached, the value
* <code>-1</code> is returned. This method blocks until input data
* is available, the end of the stream is detected, or an exception
* is thrown.
* <p>
* This method
* simply performs <code>in.read()</code> and returns the result.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public int read() throws IOException {
return in.read();
}
/**
* Reads up to <code>byte.length</code> bytes of data from this
* input stream into an array of bytes. This method blocks until some
* input is available.
* <p>
* This method simply performs the call
* <code>read(b, 0, b.length)</code> and returns
* the result. It is important that it does
* <i>not</i> do <code>in.read(b)</code> instead;
* certain subclasses of <code>FilterInputStream</code>
* depend on the implementation strategy actually
* used.
*
* @param b the buffer into which the data is read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#read(byte[], int, int)
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads up to <code>len</code> bytes of data from this input stream
* into an array of bytes. If <code>len</code> is not zero, the method
* blocks until some input is available; otherwise, no
* bytes are read and <code>0</code> is returned.
* <p>
* This method simply performs <code>in.read(b, off, len)</code>
* and returns the result.
*
* @param b the buffer into which the data is read.
* @param off the start offset in the destination array <code>b</code>
* @param len the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception NullPointerException If <code>b</code> is <code>null</code>.
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
* <code>len</code> is negative, or <code>len</code> is greater than
* <code>b.length - off</code>
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
/**
* Skips over and discards <code>n</code> bytes of data from the
* input stream. The <code>skip</code> method may, for a variety of
* reasons, end up skipping over some smaller number of bytes,
* possibly <code>0</code>. The actual number of bytes skipped is
* returned.
* <p>
* This method simply performs <code>in.skip(n)</code>.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @exception IOException if the stream does not support seek,
* or if some other I/O error occurs.
*/
public long skip(long n) throws IOException {
return in.skip(n);
}
/**
* Returns an estimate of the number of bytes that can be read (or
* skipped over) from this input stream without blocking by the next
* caller of a method for this input stream. The next caller might be
* the same thread or another thread. A single read or skip of this
* many bytes will not block, but may read or skip fewer bytes.
* <p>
* This method returns the result of {@link #in in}.available().
*
* @return an estimate of the number of bytes that can be read (or skipped
* over) from this input stream without blocking.
* @exception IOException if an I/O error occurs.
*/
public int available() throws IOException {
return in.available();
}
/**
* Closes this input stream and releases any system resources
* associated with the stream.
* This
* method simply performs <code>in.close()</code>.
*
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public void close() throws IOException {
in.close();
}
/**
* Marks the current position in this input stream. A subsequent
* call to the <code>reset</code> method repositions this stream at
* the last marked position so that subsequent reads re-read the same bytes.
* <p>
* The <code>readlimit</code> argument tells this input stream to
* allow that many bytes to be read before the mark position gets
* invalidated.
* <p>
* This method simply performs <code>in.mark(readlimit)</code>.
*
* @param readlimit the maximum limit of bytes that can be read before
* the mark position becomes invalid.
* @see java.io.FilterInputStream#in
* @see java.io.FilterInputStream#reset()
*/
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
/**
* Repositions this stream to the position at the time the
* <code>mark</code> method was last called on this input stream.
* <p>
* This method
* simply performs <code>in.reset()</code>.
* <p>
* Stream marks are intended to be used in
* situations where you need to read ahead a little to see what's in
* the stream. Often this is most easily done by invoking some
* general parser. If the stream is of the type handled by the
* parse, it just chugs along happily. If the stream is not of
* that type, the parser should toss an exception when it fails.
* If this happens within readlimit bytes, it allows the outer
* code to reset the stream and try another parser.
*
* @exception IOException if the stream has not been marked or if the
* mark has been invalidated.
* @see java.io.FilterInputStream#in
* @see java.io.FilterInputStream#mark(int)
*/
public synchronized void reset() throws IOException {
in.reset();
}
/**
* Tests if this input stream supports the <code>mark</code>
* and <code>reset</code> methods.
* This method
* simply performs <code>in.markSupported()</code>.
*
* @return <code>true</code> if this stream type supports the
* <code>mark</code> and <code>reset</code> method;
* <code>false</code> otherwise.
* @see java.io.FilterInputStream#in
* @see java.io.InputStream#mark(int)
* @see java.io.InputStream#reset()
*/
public boolean markSupported() {
return in.markSupported();
}
}
protected volatile InputStream in;
这里使用了volatile关键字,可见,输入流在获取文件的时候,是会每次都放入内存,并去内存中读取,而不是放入内存中的cache然后从cache读取数据的。关于volatile关键字,请参考