【文章标题】打造山寨产品和伪造产品的利器——装饰模式
【文章作者】曾健生
【作者邮箱】[email protected]
【作者QQ】190678908
【作者博客】http://blog.csdn.net/newjueqi
【编程环境】JDK 1.6.0_01
【作者声明】欢迎转载文章,但转载请保留文章的完整性以及注明文章的出处。
*******************************************************************************
现在“山寨文化”和“伪造产品”大行其道,“山寨春晚”“山寨明星”“山寨手机”等等,“伪造产品”就更加不用说了,广大群众也深受其害(本人也是其中一位受害者!!!)不说不知道,在学设计模式的过程中本人一不小心发现了装饰模式是有很强的“伪造功能”(“伪造功能”是指把别人写的类,功能模块等包装一下换成自己写的,但其实核心还是别人写的代码)和“山寨功能”(利用类提供的接口添加代码增强类的功能)。
《设计模式:可复用面向对象软件的基础》是这样定义装饰模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来来说,装饰模式比生成子类更加灵活。
一般来说,实现装饰器模式可分为下面3步:
1. 建立包装类,定义构造函数接收包装对象
2. 建立成员引用,让被包装对象作用于整个包装类
3.根据需要增加一些功能函数已增强类的功能
如果我们把装饰器模式实现“伪造功能”,就分为以下3步(非常邪恶的!!!):
1. 建立包装类,定义构造函数接收包装对象的参数
2. 建立成员引用,让被包装对象作用于整个包装类
3. 用自定义的函数封装包装对象的函数
以前有个著名的“汉芯造假”事件,大概就是说把某个外国芯片的标识去掉,然后改头换面,就变成了所谓的“汉芯”了。用装饰器模式实现“伪造功能”模拟这个过程的代码如下:
//一个美国芯片类,这个芯片类有三个功能:
//1.显示自身的信息
//2.获得两个数相加的结果
//3.获得两个数相减的结果
class AmericanChip
{
//美国芯片类的构造函数
AmericanChip( ){}
//美国芯片类显示自身信息的函数
public void printInfo()
{
System.out.println( "我是美国的芯片" );
}
//获得两个数相加的结果
public double add( double x, double y )
{
return x+y;
}
//获得两个数相减的结果
public double sub( double x, double y )
{
return x-y;
}
}
//这是造假芯片类,把美国的芯片改头换面后完成的
//步骤分为三步:
//1.建立包装类,定义构造函数接收包装对象的参数
//2.建立成员引用,让被包装对象作用于整个包装类
//3.用自定义的函数封装包装对象的函数
class FakeChip
{
//建立被包装类的引用
private AmericanChip americanChip;
FakeChip( )
{
//构造美国的芯片类的实例
this.americanChip=new AmericanChip();
}
//造假芯片类显示自身信息的函数,这个函数就相同于把美国芯片的标识去掉,换上“汉芯”的标识
public void printInfoFake()
{
System.out.println( "我是造假芯片" );
}
//造假芯片类通过美国芯片类获得两个数相加的结果
public double addFake( double x, double y )
{
return americanChip.add( x, y );
}
//造假芯片类通过美国芯片类获得两个数相减的结果
public double subFake( double x, double y )
{
return americanChip.sub( x, y );
}
}
造假芯片类FakeChip共暴露了3个接口:
1. public void printInfoFake()
2. public double addFake( double x, double y )
3. public double subFake( double x, double y )
从代码可看出,除了printInfoFake()方法是显示自身的信息外(造假产品要先显示一下自身的产品信息使别人误认为是正牌货),其他的两个方法addFakehe 和 subFake实际上是调用了美国芯片类的相关方法,这就模拟了“汉芯”造假的过程。
可能有的读者可能问使用继承不是能实现这个结果吗?选用装饰器模式而不用继承有两个原因:
1. 装饰器模式可选择需要暴露被包装类(美国芯片类AmericanChip)的哪些公有函数,虽然也可以用继承后覆盖同名函数的方法实现,但如果包装类(美国芯片类AmericanChip)的公有函数非常多的话,那真的写代码写得人崩溃!!!
2. 可把被包装类(美国芯片类AmericanChip)的公有函数用间接实现改名调用,真正实现改头换面的目的。
虽然本文前面论述的内容是怎么用装饰器模式实现“造假”过程,但我们必须要明白一个事实:装饰器模式的出现是为了给子类添加新的功能提供方便性和灵活性,这个才是装饰器模式的最主要用途。
在java的IO类中有个类FileInputStream用来处理文件数据的,另外还有一个类BufferedInputStream提供了缓冲区的功能,大家认真比较一下BufferedInputStream和FileInputStream的函数,发现多了缓冲的功能外,其他的实现都是很相近的,另外查找一下BufferedInputStream的构造函数BufferedInputStream
(InputStream in),能发现
BufferedInputStream的实例化是传入了InputStream的对象,大家有没有发现和前面论述的实现装饰器模式的三步非常像,虽然在源码里BufferedInputStream的实现比较麻烦,但我们可用装饰器模式的思想,实现一个“山寨版BufferedInputStream”类。
//这是山寨版的BufferedInputStream类MyBufferedInputStream
package newjueqi.net.csdn.MyBufferedInputStream;
//这是山寨版的BufferedInputStream类MyBufferedInputStream
import java.io.*;
class MyBufferedInputStream
{
private int num=0;//读入字节的个数
private int pos=0;//当前在数组中的下标
//实现装饰器模式步骤2:建立成员引用,让被包装对象作用于整个包装类
private InputStream inputstream_temp=null; //字节流的引用
private byte buf[]=new byte[1024]; //缓冲区数组
byte temp=0;
//实现装饰器模式步骤1:建立包装类,定义构造函数接收包装对象
MyBufferedInputStream( InputStream inputstream_temp )
{
this.inputstream_temp=inputstream_temp;
}
//实现装饰器模式步骤3:根据需要增加一些功能函数已增强类的功能
//用缓冲区增强了读取的功能
public int myRead()
{
//把缓冲区的数据填满
if( num==0 )
{
try {
//num中保存了读入缓冲区的字节的个数
num=inputstream_temp.read(buf);
pos=0;
//表示已读到文件尾,返回-1
if( num<0 )
return -1;
temp=buf[pos];
num--;
pos++;
//为了防止读入数据如果为11111111后类型转换后变成
//-1,所以要把多余位上的1去掉用&运算
return temp&0xff;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return -1;
}
}
else if( num>0 ) //取出缓冲区中的数据
{
temp=buf[pos];
num--;
pos++;
return temp&0xff;
}
return -1;
}
//关闭字节流
public void close()
{
try {
inputstream_temp.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
下面用这个山寨版的BufferedInputStream类MyBufferedInputStream,实现把C://1.mp3(4M)复制到D盘下,看一下复制所需的时间:
测试代码如下:
package newjueqi.net.csdn.MyBufferedInputStream;
//测试山寨版的BufferedInputStream类MyBufferedInputStream
import java.io.*;
class TestMyBufferedInputStreamDemo
{
public static void main(String args[])
{
//记录程序开始运行的时间
long starttime = System.currentTimeMillis();
//记录程序结束运行的时间
long endtime=0;
try {
FileInputStream fis=new FileInputStream( "c://1.mp3" );
MyBufferedInputStream mbis=new MyBufferedInputStream( fis );
FileOutputStream fos=new FileOutputStream( "d://1.mp3");
BufferedOutputStream bos=new BufferedOutputStream( fos );
int num=0;
while( (num=mbis.myRead())!=-1 )
{
bos.write( num );
}
//关闭流
mbis.close();
bos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//记录程序结束运行的时间
endtime=System.currentTimeMillis();
System.out.println( "山寨类MyBufferedInputStream共用时"+(endtime-starttime)+"毫秒");
}
}
运行的结果如下图1:
图1
下面我们用java提供的BufferedInputStream类实现上面的文件复制功能,看一下用了多少时间:测试代码如下:
//测试BufferedInputStream类
package newjueqi.net.csdn.MyBufferedInputStream;
import java.io.*;
class TestBufferedInputStreamDemo
{
public static void main(String args[])
{
//记录程序开始运行的时间
long starttime = System.currentTimeMillis();
//记录程序结束运行的时间
long endtime=0;
try {
FileInputStream fis=new FileInputStream( "c://1.mp3" );
BufferedInputStream mbis=new BufferedInputStream( fis );
FileOutputStream fos=new FileOutputStream( "d://1.mp3");
BufferedOutputStream bos=new BufferedOutputStream( fos );
int num=0;
while( (num=mbis.read())!=-1 )
{
bos.write( num );
}
//关闭流
mbis.close();
bos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//记录程序结束运行的时间
endtime=System.currentTimeMillis();
System.out.println( "java提供的BufferedInputStream共用时"+(endtime-starttime)+"毫秒");
}
}
运行结果如图2:
图2
当然,由于系统方面的原因(而且山寨代码也有很多情况没考虑),并不能以一次数据的测试为准,但从图1和图2的比较可看出,山寨版的BufferedInputStream类MyBufferedInputStream性能还是可以的^-^
本人热切期盼和大家多交流,另外对文章中出现的错误,敬请各位指出,联系方式:
博客:http://blog.csdn.net/newjueqi
QQ:190678908