笔试总结 :I/O流,线程,设计模式,异常。。。。
字符类型转换: eg:num——》byte public static byte[] convert(double num ) throws IOException{ byte[] data=null; ByteArrayOutputStream bos=new ByteArrayOutputStream(); DataOutputStream dos=new DataOutputStream(bos); dos.writeDouble(num); dos.flush();//刷新
data=bos.toByteArray(); dos.close();//内存中的流可以不用关闭
return data; }
String str1=new String(); String str2=new String(); System.out.println(str1.equals(str2)); 输出true;
输入输出流:
public static void main(String[] args) { try { File file =new File(\"E:/1.txt\");
FileOutputStream outStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);
BufferedOutputStream bo = new BufferedOutputStream(outStream);
bo.write(\"sdfgds\".getBytes());
bo.flush();
objectOutputStream.writeObject(\"succeful\");
outStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} }
java Map 遍历速度最优解 第一种:
Map map = new HashMap();
Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue(); }
效率高,以后一定要使用此种方式!
第二种:
Map map = new HashMap(); Iterator iter = map.keySet().iterator(); while (iter.hasNext()) { Object key = iter.next(); Object val = map.get(key); }
效率低,以后尽量少使用!
HashMap的遍历有两种常用的方法,那就是使用keyset及entryset来进行遍历,但两者的遍历速度是有差别的,对于keySet其实是遍历了2次,一次是转为iterator,一次就从hashmap中取出key所对于的value。而entryset只是遍历了第一次,他把key和value都放到了entry中,所以就快了。
注:Hashtable的遍历方法和以上的差不多!如果要保持HashMap的遍历顺序和原插入顺序一致,可以使用LinkedHashMap,使用方法和HashMap一样,改一下声明即可:LinkedHashMap myMap = new LinkedHashMap(); 当然需要导入:java.util.LinkedHashMap
软件的维护活动可以分为改正性维护、适应性维护、完善性维护和预防性维护四类,其中完善性维护是软件维护工作的主要部分;
线程:java.lang.Thread实现了Thread接口
1、程序:指令集,静态概念
2、进程:操作系统调度程序,动态概念
3、线程:在进程内多条执行路径——虚拟多线程:根据调度算法执行(不同线程间的切换)——真实多线程:多个CPU的实现多个线程eg:main方法和gc的关系,和异常的关系;也称为轻量级的进程;
一个进程的线程共享相同的内存单元,内存地址空间——》可以访问相同的变量和对象,而且他们从同一堆中分配对象——》通信,数据交换,同步操作
由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更加简单而且信息的传递速度也更快; 区别:
进程:
创建进程:
1.模拟龟头赛跑:
a、创建线程:继承Thread类和实现run方法;
b、调用线程:创建线程对象,调用其start方法;
public cla Rabbit extends Tread{
public void run(){
for(int i=0;i
System.out.print(\"兔子跑了\"+i+\"步\");
}
}
public static void main(String[] args){
创建子类对象
Rabbit rabbit=new Rabbit();
Touzi touzi=new Touzi();
rabbit.start();
touzi.start();//不能使用run方法,否则会作为一个普通的方法进行执行,即一个执行完后执行另一个
} 使用Runable创建线程
1、实现Runable接口+重写run方法
2、启动多线程,使用静态代理
1)、创建真实角色 :A a=new A();
2)、创建代理角色+真实角色的引用: Thread proxy=new Thread(a);
3)、调用.start方法:proxy.start(); 优点:
1、方便共享资源
第三种创建线程方法:实现util.concurrent下的callable接口
优点:可以抛异常(run方法不能抛异常,只能有运行时异常);执行完任务后可以有返回值;
缺点:繁琐 思路:
1)、
创建callable实现类和重写call方法
2)、借助执行调度服务ExecutorService获取Future对象
ExecutorService ser=Executors.newFixedThreadPool(2);
Future result=ser.submit(实现类对象) 3)、获取值result.get(); 4)、停止服务 ser.shutdownNow()||ser.shutdown(); 线程的状态:
停止线程方法:
1、调用终止线程方法
2、线程体里面设置标志,eg:while(flag)时再执行软方法; 优先级(priority):
MAX_PRIOPRITY 10;NORM_PRIOPRITY 5;MIN_PRIOPRITY 1;——Thread类的静态变量
设置方法:setPrioprity(Thread.MAX_PRIOPRITY);//代表的是概率没有绝对的先后顺序
.setName(线程名称);给线程起名字;getName();获取线程的名称;currentThread();获取当前运行的线程,也就是线程本身 线程的同步和并发:
定义: 多个线程管理同一个对象——确保资源的安全性Synchronized:
缺点:速度变慢了 方法1)、同步方法:public static synchronized void getNum(); 方法2)、同步语句块:synchronized(this|静态类.cla){};——注意锁定的范围大小,不能锁定资源对象的一部分内容
难点:范围大小的选择
死锁:只有在线程同步中抢夺资源才会造成死锁(概率事件); 解决死锁:生产者消费者模式
1、管城控制;一个容器控制
2、标志位法:object 的wait和notify方法+synchronized一起使用
任务调度:timer类——util.Timer类
IO流:
IO流的三种分类方式
1.按流的方向分为:输入流和输出流 2.按流的数据单位不同分为:字节流和字符流 3.按流的功能不同分为:节点流和处理流(节点流表示的是直接操作节点(例如文件,键盘)的流,例如FileInputStream.处理流(过滤流)表示的是对节点流进行了操作(加工)的类,例如InputStreamReader) IO流的四大抽象类
字符流:Reader(读) Writer(写)
字节流:InputStream(读数据) OutputStream(写数据) IO体系,所具备的基本功能就有两个:读和写 1,字节流
InputStream(读),OutputStream(写) 2,字符流
Reader(读),Writer(写) 结论:只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
缓冲流:缓冲流要套接在相应的节点流之上,提高了读写的效率。
此处理流的构造方法都得传相对应的基类类型
BufferedReader:提供了readLine方法用于高校读取一行字符串 BufferedWriter:提供了newLine用于写入一个行分隔符也就是换行 BufferedInputStream 没多大用处 BufferedOutputStream 没多大用处
转换流:主要作用将字节流转换成字符流。用处较大! 转换流在构造时可以指定其编码集合 InputStreamReader需要和InputStream套接 OutputStreamWriter需要和OutputStream套接
例:OutputStreamWriter osw = new OutputStreamWriter (new FileOutputStream(文件路径);
方法例:osw.getEncoding(); 获得流的编码方式 数据流与字节数组流
数据流主要为实现可以存取Java原始数据类型如long,boolean 数据流是字节流
DataInputStream需要和InputStream套接 DataOutputStream需要和OutputStream套接
DataInputStream方法:readBoolean() readInt()… readUTF():网络传输常用方法 读一个Unicode字符串 DataOutputStream方法与DataInputStream基本对应为写的方法 //此构造函数等于已可以往一个字节数组里输入内容
ByteArrayOutputStream baos = new ByteArrayOutputStream (); //此方法为获取一个字节数组方法返回字节数组 baos.toByteArray();
//此方法获取字节数组占了多少字节
new ByteArrayInputStream(一个字节数组)。available() ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos);
public void countCharacter() throws IOException {
File fileSrc= new File(dir,\"file01.txt\");
BufferedReader fin=new BufferedReader( new FileReader(fileSrc) );
String line;
while( (line=fin.readLine())!=null){
Map counter= new HashMap();
int len = line.length();
for(int i=0; i
char c=line.charAt(i);
if(!( (c>=\'0\' && c=\'A\' && c=\'a\' && c
continue;
}
if(counter.containsKey(c)){
counter.put(c, counter.get(c)+1);
}else{
counter.put(c, 1);
}
}
for(Iterator it=counter.keySet().iterator(); it.hasNext(); ){
char key=it.next();
int count=counter.get(key);
System.out.println(key+\" --- \"+count);
}
}
fin.close();
}
public void copy() throws IOException{ BufferedReader bi=new BufferedReader(new FileReader(\"d:/fileTest/file01.txt\")); File dir=new File(\"d:/fileTest/\"); if (!dir.exists()) { dir.mkdir(); } File afile=new File(dir,\"file02.txt\"); BufferedWriter bw=new BufferedWriter(new FileWriter(afile));
String line=null;
if ( (line=bi.readLine())!=null) {
bw.write(line);
}
bi.close();
bw.close(); }
public void fileOutput(String str) throws IOException{
File afile=null;
if (!dir.exists()) {
dir.mkdirs(); } afile=new File(dir,\"file01.txt\");
fout=new FileOutputStream(afile);
objOut=new ObjectOutputStream(fout ) ;
objOut.write( str.getBytes(\"utf-8\")) ;
objOut.flush();
if (objOut!=null) {
objOut.close();
}
if (fout!=null) {
fout.close();
} }
设计模式(解决类和类之间的问题)之静态代理:
1、有真实角色(重写主要的方法):eg:找房子的人、结婚者
2、有代理角色(持有真实对象的引用(可以通过构造器调用此方法),写次要的方法):eg:中介、婚庆公司
3、二者实现相同的接口
设计模式之单例模式——确保类只有一个对象
异常处理:
出现原因:处理程序本身不需要考虑的问题,将和程序本身不需要考虑的问题交给一场机制处理;
抛出异常:在执行一个方法中,出此案异常时则这个方法就生成一个代表该异常对象,停止当前的执行路径,并将异常对象提交给JRE;
捕获异常:jre得到该异常后,寻找相应的代码来处理异常,jre在方法的调用栈中查找,从生成异常的方法中回朔,知道找到异常处理的代码为止;
error:解决方法:唯一的方法:重新启动;
unCheckException eg:int i=1/0;非捕获异常——runtimeException
checkedException被编译器检查的异常;
捕获异常try...catch(exception e)....finally..异常处理方式1)、手动处理2)、声明异常;
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.ArithmeticException的异常。
Java中的异常用对象来表示。Java对异常的处理是按异常分类处理的,不同异常有不同的分类,每种异常都对应一个类型(cla),每个异常都对应一个异常(类的)对象。
异常类从哪里来?有两个来源,一是基本异常类型,二是用户通过继承Exception类或者其子类自己定义的异常。
异常的对象从哪里来呢?有两个来源,一是Java运行时环境自动抛出系统生成的异常,而不管你是否愿意捕获和处理,它总要被抛出!比如除数为0的异常。二是程序员自己抛出的异常,这个异常可以是程序员自己定义的,也可以是Java语言中定义的,用throw 关键字抛出异常,这种异常常用来向调用者汇报异常的一些信息。
异常是针对方法来说的,抛出、声明抛出、捕获和处理异常都是在方法中进行的。
Java异常处理的目的是提高程序的健壮性,你可以在catch和finally代码块中给程序一个修正机会,使得程序不因异常而终止或者流程发生以外的改变。同时,通过获取Java异常信息,也为程序的开发维护提供了方便,一般通过异常信息就很快就能找到出现异常的问题(代码)所在。
Java异常处理是Java语言的一大特色,也是个难点,掌握异常处理可以让写的代码更健壮和易于维护。
二、Java异常类类图
下面是这几个类的层次图:
java.lang.Object java.lang.Throwable java.lang.Exception
java.lang.RuntimeException java.lang.Error
java.lang.ThreadDeath
1、Throwable
Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
两个子类的实例,Error 和 Exception,通常用于指示发生了异常情况。通常,这些实例是在异常情况的上下文中新近创建的,因此包含了相关的信息(比如堆栈跟踪数据)。
三、Java异常处理机制
对于可能出现异常的代码,有两种处理办法:
第
一、在方法中用try...catch语句捕获并处理异常,catach语句可以有多个,用来匹配多个异常。
第
二、对于处理不了的异常或者要转型的异常,在方法的声明处通过throws语句抛出异常。
如果每个方法都是简单的抛出异常,那么在方法调用方法的多层嵌套调用中,Java虚拟机会从出现异常的方法代码块中往回找,直到找到处理该异常的代码块为止。然后将异常交给相应的catch语句处理。如果Java虚拟机追溯到方法调用栈最底部main()方法时,如果仍然没有找到处理异常的代码块,将按照下面的步骤处理:
第
一、调用异常的对象的printStackTrace()方法,打印方法调用栈的异常信息。
第
二、如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
通过分析思考可以看出,越早处理异常消耗的资源和时间越小,产生影响的范围也越小。因此,不要把自己能处理的异常也抛给调用者。
还有一点,不可忽视:finally语句在任何情况下都必须执行的代码,这样可以保证一些在任何情况下都必须执行代码的可靠性。比如,在数据库查询异常的时候,应该释放JDBC连接等等。finally语句先于return语句执行,而不论其先后位置,也不管是否try块出现异常。finally语句唯一不被执行的情况是方法执行了System.exit()方法。System.exit()的作用是终止当前正在运行的 Java 虚拟机。finally语句块中不能通过给变量赋新值来改变return的返回值,也建议不要在finally块中使用return语句,没有意义还容易导致错误。
最后还应该注意一下异常处理的语法规则:
第
一、try语句不能单独存在,可以和catch、finally组成 try...catch...finally、try...catch、try...finally三种结构,
第
二、try、catch、finally三个代码块中变量的作用域分别独立而不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
第三、多个catch块时候,Java虚拟机会匹配其中一个异常类或其子类,就执行这个catch块,而不会再执行别的catch块。
第
四、throw语句后不允许有紧跟其他语句,因为这些没有机会执行。
第五、如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出。
throw和throws关键字的区别 throw用来抛出一个异常,在方法体内。语法格式为:throw 异常对象。
throws用来声明方法可能会抛出什么异常,在方法名后,语法格式为:throws 异常类型1,异常类型2...异常类型n。
四、如何定义和使用异常类
1、使用已有的异常类,假如为IOException、SQLException。
try{ 程序代码
}catch(IOException ioe){ 程序代码
}catch(SQLException sqle){ 程序代码 }finally{ 程序代码 }
2、自定义异常类
创建Exception或者RuntimeException的子类即可得到一个自定义的异常类。例如: public cla MyException extends Exception{ public MyException(){} public MyException(String smg){ super(smg); } }
3、使用自定义的异常
用throws声明方法可能抛出自定义的异常,并用throw语句在适当的地方抛出自定义的异常。例如: 在某种条件抛出异常
public void test1() throws MyException{ ... if(....){
throw new MyException(); } }
将异常转型(也叫转译),使得异常更易读易于理解
public void test2() throws MyException{ ... try{ ... }catch(SQLException e){ ... throw new MyException(); } }
还有一个代码,很有意思:
public void test2() throws MyException{ ... try { ... } catch (MyException e) { throw e; } }
这段代码实际上捕获了异常,然后又和盘托出,没有一点意义,如果这样还有什么好处理的,不处理就行了,直接在方法前用throws声明抛出不就得了。异常的捕获就要做一些有意义的处理。
五、运行时异常和受检查异常
Exception类可以分为两种:运行时异常和受检查异常。
1、运行时异常
RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。例如,当除数为零时,就会抛出java.lang.ArithmeticException异常。
2、受检查异常
除了RuntimeException类及其子类外,其他的Exception类及其子类都属于受检查异常,这种异常的特点是要么用try...catch捕获处理,要么用throws语句声明抛出,否则编译不会通过。 六.处理异常机制
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
抛出异常(throw):当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同。
由于运行时异常的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。
能够捕捉异常的方法,需要提供相符类型的异常处理器。所捕捉的异常,可能是由于自身语句所引发并抛出的异常,也可能是由某个调用的方法或者Java运行时 系统等抛出的异常。也就是说,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。
任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的throw语句抛出异常。
从方法中抛出的任何异常都必须使用throws子句。
捕捉异常通过try-catch语句或者try-catch-finally语句实现。
总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。
七、Java异常处理的原则和技巧
1、避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常。
2、细化异常的类型,不要不管什么类型的异常都写成Excetpion。
3、catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获到后要么处理,要么转译,要么重新抛出新类型的异常。
4、不要把自己能处理的异常抛给别人。
5、不要用try...catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况。
若将父类构造器设置为私有则此类无法被继承;但是在自己的类内部私有的无参构造器是可以使用的;
Implicit super constructor Parent() is not visible for default constructor.Must define an explicit constructor
设计模式——解决类和类之间的问题:
静态代理,动态代理,单例模式,工厂模式,组合模式,
1、静态代理:(使用Runable创建线程)
1)有真实角色(重写主要的方法):eg:找房子的人、结婚者
2)有代理角色(持有真实对象的引用(可以通过构造器调用此方法),写次要的方法):eg:中介、婚庆公司
3)二者实现相同的接口
eg: 创建真实角色 :A a=new A(); 创建代理角色+真实角色的引用: Thread proxy=new Thread(a); 调用.start方法:proxy.start();
2、动态代理:
观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。
Proxy.newProxyInstance(); 参数说明:
Object proxy:指被代理的对象。 Method method:要调用的方法 Object[] args:方法调用时所需要的参数
可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。 Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:
public static Object newProxyInstance(ClaLoader loader, Cla<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 参数说明:
ClaLoader loader:类加载器 Cla<?>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler接口的子类实例 Ps:类加载器
在Proxy类中的newProxyInstance()方法中需要一个ClaLoader类的实例,ClaLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器; Booststrap ClaLoader:此加载器采用C++编写,一般开发中是看不到的; Extendsion ClaLoader:用来进行扩展类的加载,一般对应的是jre\\lib\\ext目录中的类; AppClaLoader:(默认)加载clapath指定的类,是最常使用的是一种加载器。 动态代理
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。 动态的编译如下代码:
3、单例模式——确保类只有一个对象
:
BeanFactory:产生一个新的实例,可以实现单例模式。
eg: public cla SingleTon { //定义私有的构造器 private SingleTon(){} //生成静态的引用变量
private static SingleTon singleTon=new SingleTon(); //返回引用变量
public SingleTon getSingleTon(){return singleTon;} // private static SingleTon instance=null; // // public static synchronized SingleTon getInstance(){ // if (instance==null) // instance=new SingleTon(); // return instance; // } }
4、Factory(工厂模式)—— 工厂模式就是专门负责将大量有共同接口的类实例化,而且不必事先知道每次是要实例化哪一个类的模式 private static SeionFactory sf=null; static {
AnnotationConfiguration config=new AnnotationConfiguration().configure(); sf=config.buildSeionFactory(); }
public static Seion getSeion(){ return sf.openSeion(); }
seion.getTransaction().begin();
seion.update(obj);
seion.getTransaction().commit();
1.目的
工厂模式就是专门负责将大量有共同接口的类实例化,而且不必事先知道每次是要实例化哪一个类的模式。它定义一个用于创建对象的接口,由子类决定实例化哪一个类。 2 .简单工厂模式的结构
3.一个简单例子 java 代码
1.// 产品接口
2.public interface Product { 3.4.
public void getName(); 5.6.7.8.9.
}
// 具体产品A
public cla ProductA implements Product {
10.
11.
public void getName() {
12. System.out.println(\" I am ProductA \"); 13. } 14.
15.} 16.
17.// 具体产品B
18.public cla ProductB implements Product { 19.
20.
public void getName() {
21. System.out.println(\" I am ProductB \"); 22. } 23.
24.} 25.
26.// 工厂类
27.public cla ProductCreator {
28.
29.
public Product createProduct(String type) { 30.
if (\" A \".equals(type)) { 31.
return new ProductA(); 32. }
33.
if (\" B \".equals(type)) { 34.
return new ProductB(); 35. } else
36.
return null;
37. } 38.
39.
public static void main(String[] args) {
40.
ProductCreator creator = new ProductCreator(); 41. creator.createProduct(\" A \").getName(); 42. creator.createProduct(\" B \").getName();
43. } 44.}
5、组合模式eg:arraylist ,复制:复制文件盒复制文件夹的动作一样但底层实现不一样
组合模式(Composite)属于结构性模式,它描述了对象间的组合关系。 对象间常常通过树结构来组织(包含)起来,以实现整体-部分的层次结构。整体上可以看做是一个组合对象。
抛却各种复杂的术语,组合模式的特点是: 对象通过实现(继承)统一的接口(抽象类),调用者对单一对象和组合对象的操作具有一致性。
Adapter(适配器模式) Proxy(代理模式),