这是我参与更文挑战的第25天,活动详情查看: 更文挑战
设计模式
装饰者模式
何为装饰者模式?用通俗的话说就是给对象加上装饰,不改变它原本结构的同时让他动态的获得新的功能。装饰类和被装饰类可以独立发展,不会相互耦合。
举个例子:早晨上班吃早餐,公司门口有卖煎饼果子的,我们买的时候可以加肠、加蛋、加辣条,这里煎饼果子就是被装饰类,而附加的肠、蛋、辣条就是装饰类。
现在我们早餐店有煎饼果子(5元)、肉夹馍(6元)、鸡蛋灌饼(7元),而这些都可以选择加肠(2元)、加蛋(1元)、加辣条(0.5元)让它更加美味,那么组合之后的价格是多少呢?
如果使用传统的方式,组合的种类太多,导致类的递增,从而提高项目的维护性。
首先我们将煎饼果子、肉夹馍、鸡蛋灌饼抽象成Eat对象,并且有两个属性des和price,一个方法cost供子类继承
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public abstract class Eat {
public String des;
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public abstract float cost();
}
并且加了缓冲层,将具体类(煎饼果子、肉夹馍、鸡蛋灌饼)的共同点抽取出来
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class Food extends Eat {
@Override
public float cost() {
return super.getPrice();
}
}
煎饼果子Pancake
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class Pancake extends Food {
public Pancake() {
setDes("煎饼果子");
setPrice(5.0f);
}
}
肉夹馍
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class ChineseHamburger extends Food {
public ChineseHamburger() {
setDes("肉夹馍");
setPrice(6.0f);
}
}
然后就是装饰者Decorator继承并组合被装饰者Eat,这里的cost就可以递归的计算出组合之后商品的价格。
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class Decorator extends Eat {
private Eat eat;
public Decorator(Eat eat) {
this.eat = eat;
}
@Override
public float cost() {
return super.getPrice() + eat.cost();
}
@Override
public String getDes() {
return super.des + "(" + super.getPrice() + ")加" + eat.getDes();
}
}
肠Sausage
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class Sausage extends Decorator {
public Sausage(Eat eat) {
super(eat);
setDes("加香肠");
setPrice(2.0f);
}
}
辣条SpicyStrips
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class SpicyStrips extends Decorator {
public SpicyStrips(Eat eat) {
super(eat);
setDes("加辣条");
setPrice(0.5f);
}
}
鸡蛋Egg
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class Egg extends Decorator {
public Egg(Eat eat) {
super(eat);
setDes("加鸡蛋");
setPrice(1.0f);
}
}
main
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class Buy {
public static void main(String[] args) {
Eat eat = new Pancake();
System.out.println(eat.getDes() + ":" + eat.cost());
eat = new Egg(eat);
System.out.println(eat.getDes() + "的价格:" + eat.cost());
eat = new SpicyStrips(eat);
System.out.println(eat.getDes() + "的价格:" + eat.cost());
eat = new Sausage(eat);
System.out.println(eat.getDes() + "的价格:" + eat.cost());
}
}
可以轻松的实现我们想要的效果。而且当我们增加新的食品的时候,也有良好的扩展性。当我们增加鸡蛋灌饼,
只需要增加EggCake
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class EggCake extends Food {
public EggCake() {
setDes("鸡蛋灌饼");
setPrice(7.0f);
}
}
main
package com.wangscaler.decorator;
/**
* @author wangscaler
* @date 2021.06.28 10:28
*/
public class Buy {
public static void main(String[] args) {
Eat eat = new EggCake();
System.out.println(eat.getDes() + ":" + eat.cost());
eat = new Egg(eat);
System.out.println(eat.getDes() + "的价格:" + eat.cost());
eat = new SpicyStrips(eat);
System.out.println(eat.getDes() + "的价格:" + eat.cost());
eat = new Sausage(eat);
System.out.println(eat.getDes() + "的价格:" + eat.cost());
}
}
轻松实现。
那么装饰者Decorator是怎么递归实现价格的计算的呢?
1、看我们的main函数,起初我们new EggCake();,此时EggCake通过继承Food,而Food继承Eat,通过父类的Eat的setDes和setPrice将描述和价格写入,而此时Decorator也继承了Eat,所以在Decorator的cost()方法和getDes()方法中 super.des和super.getPrice()拿到的其实是EggCake的描述和价格,也就是加鸡蛋和1.0f。
2、main函数new Egg(eat);中调用的Egg类的super(eat);也就是Decorator的eat,此时通过构造器public Decorator(Eat eat) {this.eat = eat;}
将EggCake的eat赋值给组合的对象eat,所以Decorator中的eat.cost();就是上一步EggCake中eat.cost()已经计算完的结果也就是7.0f。
3、所以Decorator中的cost执行完之后,将返回的值为1.0f+7.0f为8.0f。
4、下述代码重复以上操作。
源码中的装饰者模式
JDK中的FilterInputStream
抽象类InputStream类似于上述的Eat
package java.io;
public abstract class InputStream implements Closeable {
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
public int available() throws IOException {
return 0;
}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
return false;
}
}
具体类有很多。FileInputStream、ByteArrayInputStream等等,类似于我们上述的Pancake、ChineseHamburger、EggCake这里以FileInputStream为例。
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
public
class FileInputStream extends InputStream
{
private final FileDescriptor fd;
private final String path;
private FileChannel channel = null;
private final Object closeLock = new Object();
private volatile boolean closed = false;
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
public FileInputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkRead(fdObj);
}
fd = fdObj;
path = null;
fd.attach(this);
}
private native void open0(String name) throws FileNotFoundException;
private void open(String name) throws FileNotFoundException {
open0(name);
}
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
public native long skip(long n) throws IOException;
public native int available() throws IOException;
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
public final FileDescriptor getFD() throws IOException {
if (fd != null) {
return fd;
}
throw new IOException();
}
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, true, false, this);
}
return channel;
}
}
private static native void initIDs();
private native void close0() throws IOException;
static {
initIDs();
}
java.io.FileInputStream#close()
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
/* if fd is shared, the references in FileDescriptor
* will ensure that finalizer is only called when
* safe to do so. All references using the fd have
* become unreachable. We can call close()
*/
close();
}
}
}
FilterInputStream类似于上述的Decorator,在这里继承并组合了我们的抽象类InputStream
package java.io;
public
class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
public long skip(long n) throws IOException {
return in.skip(n);
}
public int available() throws IOException {
return in.available();
}
public void close() throws IOException {
in.close();
}
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
public synchronized void reset() throws IOException {
in.reset();
}
public boolean markSupported() {
return in.markSupported();
}
}
而BufferedInputStream、DataInputStream等等就相当于上述的Egg、Sausage、SpicyStrips,这里以BufferedInputStream为例
package java.io;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public
class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
protected volatile byte buf[];
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
protected int count;
protected int pos;
protected int markpos = -1;
protected int marklimit;
private InputStream getInIfOpen() throws IOException {
InputStream input = in;
if (input == null)
throw new IOException("Stream closed");
return input;
}
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else { /* grow buffer */
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
// Can't replace buf if there was an async close.
// Note: This would need to be changed if fill()
// is ever made accessible to multiple threads.
// But for now, the only way CAS can fail is via close.
// assert buf == null;
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private int read1(byte[] b, int off, int len) throws IOException {
int avail = count - pos;
if (avail <= 0) {
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, do not bother to copy the
bytes into the local buffer. In this way buffered streams will
cascade harmlessly. */
if (len >= getBufIfOpen().length && markpos < 0) {
return getInIfOpen().read(b, off, len);
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}
public synchronized int read(byte b[], int off, int len)
throws IOException
{
getBufIfOpen(); // Check for closed stream
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
// if not closed but no bytes available, return
InputStream input = in;
if (input != null && input.available() <= 0)
return n;
}
}
public synchronized long skip(long n) throws IOException {
getBufIfOpen(); // Check for closed stream
if (n <= 0) {
return 0;
}
long avail = count - pos;
if (avail <= 0) {
// If no mark position set then don't keep in buffer
if (markpos <0)
return getInIfOpen().skip(n);
// Fill in buffer to save bytes for reset
fill();
avail = count - pos;
if (avail <= 0)
return 0;
}
long skipped = (avail < n) ? avail : n;
pos += skipped;
return skipped;
}
public synchronized int available() throws IOException {
int n = count - pos;
int avail = getInIfOpen().available();
return n > (Integer.MAX_VALUE - avail)
? Integer.MAX_VALUE
: n + avail;
}
public synchronized void mark(int readlimit) {
marklimit = readlimit;
markpos = pos;
}
public synchronized void reset() throws IOException {
getBufIfOpen(); // Cause exception if closed
if (markpos < 0)
throw new IOException("Resetting to invalid mark");
pos = markpos;
}
public boolean markSupported() {
return true;
}
public void close() throws IOException {
byte[] buffer;
while ( (buffer = buf) != null) {
if (bufUpdater.compareAndSet(this, buffer, null)) {
InputStream input = in;
in = null;
if (input != null)
input.close();
return;
}
// Else retry in case a new buf was CASed in fill()
}
}
}
从以上可以看出,这个FilterInputStream就是装饰者模式。
总结
1、装饰者模式就是动态的将新功能附加到对象身上,比如我们例子中的加肠、加蛋。
2、也能体现开闭原则,比如我们增加的EggCake。
3、装饰类和被装饰类可以独立发展,而不会相互耦合
4、 使用场景:扩展一个类的功能或者动态增加功能即插即用,动态撤销。
装饰器模式主要包含以下角色。
- 抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象,即上述的Eat。
- 具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责,即上述的EggCake等。
- 抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能,即上述的Decorator。
- 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任,及上述的Egg等。