@20190904
1、什么是设计模式?你用过或者了解哪些设计模式?
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
单例模式:
在项目中用过单例模式,比如在刚开始学习mybatis框架的时候,那时候还没有整合spring框架,所以SqlSessionFactory还是需要自己去创建的,当时知道SqlSessionFactory是重量级的对象,所以为了提高性能,我是把单例模式用到了创建SqlSessionFactory这个对象中的。
工厂模式:
在答题系统中创建目录时用到了抽象工厂模式,做了一个目录抽象工厂,因为在创建大量课程的时候,可能也会创建大量的目录级别,所以就做了个抽象工厂专门生成目录,没用抽象工厂时,就是我可以直接把客户端请求创建的目录直接交给抽象工厂创建,不用在业务类进行判断创建,降低了耦合性吧,还有就是万一系统需要更多一级的目录,比如四级目录的话,我可以直接在抽象工厂写多一个抽象方法就行了,易于系统的扩展我觉得。 之所以交给抽象工厂还因为面向对象的五个基本原则,单一原则。
abstract class Factory{
abstract public static function createOne(); //创建一级目录
abstract public static function createTwo(); //创建二级目录
}
class ProductFactory extends Factory{
public static function createOne(){
return new FirstCategory();
}
public static function createTwo(){
return new SecondCategory();
}
}
单例设计模式:在项目中,单例是必不可少的。比如UIApplication、NSUserDefaults就是苹果提供的单例。在项目中经常会将用户数据管理封装成一个单例类,因此用户的信息需要全局使用。
MVC设计模式:现在绝大部分项目都是基于MVC设计模式的,现在有一部分开发者采用MVVM、MVP等模式。
通知(NSNotification)模式:通知在开发中是必不可少的,对于跨模块的类交互,需要使用通知;对于多对多的关系,使用通知更好实现。
工厂设计模式:在我的项目中使用了大量的工厂设计模式,特别是生成控件的API,都已经封装成一套,全部是扩展的类方法,可简化很多的代码。
KVC/KVO设计模式:有的时候需要监听某个类的属性值的变化而做出相应的改变,这时候会使用KVC/KVO设计模式。在项目中,我需要监听model中的某个属性值的变化,当变化时,需要更新UI显示,这时候使用KVC/KVO设计模式就很方便了。
2、什么是单例模式?单例模式实现的方式有哪些?【不下7种】 饿汉模式和懒汉模式实现方式有什么区别?
单例模式的概念:
单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
单例模式——8种实现方式
饿汉式2种(静态常量饿汉式、静态代码块饿汉式)
懒汉式3种(线程不安全懒汉式、线程安全懒汉式、同步代码块懒汉式)
还有3种(双重检查、静态内部类、枚举方式)
// 1.静态常量饿汉式
public class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
优点:实现较为简单,在类加载时就完成了实例化,避免了多线程同步问题
缺点:在类加载时就完成了实例化(使类加载的情况有很多种,不一定是调用getInstance()方法使类进行加载的),没有达到懒加载的效果。如果程序从始至终未用到该实例,则造成了空间浪费
//2.静态代码块饿汉式
public class Singleton{
private static Singleton singleton;
static{
singleton = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
优缺点:同静态常量饿汉式
//3.线程不安全懒汉式
public class Singleton{
private Singleton(){}
private static Singleton singleton;
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
优缺点:起到懒加载的效果,但是只适合在单线程下使用(开发中不推荐使用)
//4.线程安全懒汉式
public class Singleton{
private Singleton(){}
private static Singleton singleton;
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
优点:起到懒加载的效果,线程安全
缺点:调用效率低(开发中不推荐使用)
//5.同步代码块懒汉式
public class Singleton{
private Singleton(){}
private static Singleton singleton;
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}
优点:懒加载
缺点:不适合多线程环境,可能因多个线程同时到达if(singleton == null)而产生多个实例,表面代码看线程安全实际线程不安全
//6.双重检查(推荐使用)
public class Singleton{
private Singleton(){}
private static volatile Singleton singleton;
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
优点:解决了同步代码块方式的线程安全问题
//7.静态内部类(推荐使用)
静态内部类的特点:
1.当外部类被装载时,静态内部类不会被立即装载
2.当调用getInstance()时静态内部类只被装载一次
public class Singleton{
private Singleton(){}
private static class SingletonInstance{
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.singleton;
}
}
优点:当外部类Singleton被装载时,静态内部类不会立即被装载,而是在需要时才被装载,也就是调用getInstance()时才被装载,达到了懒加载的效果,这种方式采用了类加载机制来保证初始化实例时只有一个线程,所以在这里JVM
//8.枚举方式(推荐使用)
enum Singleton{
INSTANCE;
public void method(){
// 操作方法
}
}
优点:线程安全,效率高,还可防止反序列化重新创建新的对象
单例模式的应用场景:
需要被频繁创建或销毁的对象
创建对象时耗时或者耗费资源过多(即重量级对象),但又经常使用的对象
频繁访问的数据库或文件
关键点:
1)一个类只有一个实例 这是最基本的
2)它必须自行创建这个实例
3)它必须自行向整个系统提供这个实例
两种实现方式:
1 懒汉模式(类加载时不初始化)
package Singleton;
public class LazySingleton {
//懒汉式单例模式
//比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
private static LazySingleton intance = null;//静态私用成员,没有初始化
private LazySingleton()
{
//私有构造函数
}
public static synchronized LazySingleton getInstance() //静态,同步,公开访问点
{
if(intance == null)
{
intance = new LazySingleton();
}
return intance;
}
}
关键点:(代码注释上已给出)
1)构造函数定义为私有----不能在别的类中来获取该类的对象,只能在类自身中得到自己的对象
2)成员变量为static的,没有初始化----类加载快,但访问类的唯一实例慢,static保证在自身类中获取自身对象
3)公开访问点getInstance: public和synchronized的-----public保证对外公开,同步保证多线程时的正确性(因为类变量不是在加载时初始化的)
优缺点见代码注释。
2 饿汉式单例模式(在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快)
package Singleton;
public class EagerSingleton {
//饿汉单例模式
//在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化
private EagerSingleton()
{
//私有构造函数
}
public static EagerSingleton getInstance() //静态,不用同步(类加载时已初始化,不会有多线程的问题)
{
return instance;
}
}
关键点:(代码注释已写)
1)私有构造函数
2)静态私有成员–在类加载时已初始化
3)公开访问点getInstance-----不需要同步,因为在类加载时已经初始化完毕,也不需要判断null,直接返回
优缺点见代码注释。
3、单例模式如何保证线程安全?
public class Singleton {
private volatile Singleton instance = null;
//私有构造函数
private Singleton(){};
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4、你用过volatile关键字,它的是如何实现的?【涉及一些底层硬件的实现机制】
工作内存Work Memory其实就是对CPU寄存器和高速缓存的抽象,或者说每个线程的工作内存也可以简单理解为CPU寄存器和高速缓存。
那么当写两条线程Thread-A与Threab-B同时操作主存中的一个volatile变量i时,Thread-A写了变量i,那么:
- Thread-A发出LOCK#指令 发出的LOCK#指令锁总线(或锁缓存行),同时让Thread-B高速缓存中的缓存行内容失效
Thread-A向主存回写最新修改的i Thread-B读取变量i,那么:
Thread-B发现对应地址的缓存行被锁了,等待锁的释放,缓存一致性协议会保证它读取到最新的值
由此可以看出,volatile关键字的读和普通变量的读取相比基本没差别,差别主要还是在变量的写操作上。
5、工厂模式?如何实现?
什么是工厂模式?
工厂模式就是将对象的创建交由工厂来实现,程序只管使用其中具体的方法即可。
1.1 工厂模式的实现方式
1.工厂模式分为:简单工厂,工厂方法,抽象工厂
2.1 简单工厂
简单工厂的特点:工厂类是一个具体的类,通过工厂类的主要方法,如getInstance(),你用switch 或者if…else来创建所需要的对象。该实现方法不利于维护和扩展。比如需要怎么加一个DB2数据源,则需要添加具体产品子类并且需要修改工厂类,不好扩展。
//定义数据源接口
interface DataSource{
public void getDataSource();
}
//定义mysql数据源实现类
class MysqlDataSource implements DataSource{
@Override
public void getDataSource() {
System.out.println("我是mysql数据源");
}
}
//定义sqlserver数据源实现类
class SqlServerDataSource implements DataSource{
@Override
public void getDataSource() {
System.out.println("我是sqlServer数据源");
}
}
//定义oracle数据源实现类
class OracleDataSource implements DataSource{
@Override
public void getDataSource() {
System.out.println("我是oracle数据源");
}
}
//定义获取数据源的工厂
class DataSourceFactory{
public static final String TYPE_MYSQL="mysql";
public static final String TYPE_SQLSERVER="sqlserver";
public static final String TYPE_ORACLE="oracle";
public static DataSource getInstance(String type){
switch (type) {
case TYPE_MYSQL:
return new MysqlDataSource();
case TYPE_SQLSERVER:
return new SqlServerDataSource();
default:
return new OracleDataSource();
}
}
}
//测试
public class DataSourceTest {
public static void main(String[] args) {
DataSource instance = DataSourceFactory.getInstance(DataSourceFactory.TYPE_MYSQL);
instance.getDataSource();//输出 “我是mysql数据源”
}
}
(1)简单工厂模式的编写步骤
创建产品接口,如 DataSource
创建具体的产品子类,如MysqlDataSource,SqlServerDataSource,OracleDataSource
创建工厂类,如 DataSourceFactory
2.2 工厂方法
工厂方法是把简单工厂拆分为两层,一层是抽象工厂,一层是抽象工厂的子类。工厂方法利于软件的维护和二次开发。如果有新需求只需要添加对应的子类,而不是修改已有的类,比如增加一个DB2数据源,只需要添加一个DB2产品子类,和DB2的工厂子类就可以了。
//定义数据源接口
interface DataSource{
public void getDataSource();
}
//定义mysql数据源实现类
class MysqlDataSource implements DataSource{
@Override
public void getDataSource() {
System.out.println("我是mysql数据源");
}
}
//定义sqlserver数据源实现类
class SqlServerDataSource implements DataSource{
@Override
public void getDataSource() {
System.out.println("我是sqlServer数据源");
}
}
//定义oracle数据源实现类
class OracleDataSource implements DataSource{
@Override
public void getDataSource() {
System.out.println("我是oracle数据源");
}
}
//定义工厂抽象类
abstract class DataSourceFactory{
public abstract DataSource create();
}
//定义mysql数据源工厂类
class MysqlDataSourceFactory extends DataSourceFactory{
@Override
public DataSource create() {
return new MysqlDataSource();
}
}
//定义sqlserver数据源工厂类
class SqlServerDataSourceFactory extends DataSourceFactory{
@Override
public DataSource create() {
return new SqlServerDataSource();
}
}
//定义oracle数据源工厂类
class OracleDataSourceFactory extends DataSourceFactory{
@Override
public DataSource create() {
return new OracleDataSource();
}
}
//测试
public class DataSourceTest {
public static void main(String[] args) {
OracleDataSourceFactory dataSourceFactory=new OracleDataSourceFactory();
DataSource create = dataSourceFactory.create();
create.getDataSource();//输出 “我是oracle数据源”
}
}
(1)工厂方法的创建步骤
创建产品接口,如 DataSource
创建具体的产品子类,如MysqlDataSource,SqlServerDataSource,OracleDataSource
创建抽象工厂类,如DataSourceFactory,其中create() 为获取对象实例
创建具体的工厂子类,如MysqlDataSourceFactory,SqlServerDataSourceFactory,OracleDataSourceFactory
2.3 抽象工厂
抽象工厂针对的是多产品,每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例。
public class Test {
public static void main(String[] args) {
DnFactory dnFactory=new DnFactory();
IPen createPen = dnFactory.createPen();
createPen.desc(); //输出 “我是低档钢笔”
}
}
//定义一个钢笔接口
interface IPen{
public void desc();
}
//定义一个高档钢笔
class UpPen implements IPen{
@Override
public void desc() {
System.out.println("我是高档钢笔");
}
}
//定义一个低档钢笔
class DnPen implements IPen{
@Override
public void desc() {
System.out.println("我是低档钢笔");
}
}
//定义一个铅笔接口
interface IPencil{
public void desc();
}
//定义一个高档铅笔
class UpPencil implements IPencil{
@Override
public void desc() {
System.out.println("我是高档铅笔");
}
}
//定义一个低档铅笔
class DnPencil implements IPencil{
@Override
public void desc() {
System.out.println("我是低档铅笔");
}
}
//定义抽象工厂
abstract class IFactory{
abstract IPen createPen();
abstract IPencil createPencil();
}
//定义高档工厂实现
class Upfactory extends IFactory{
@Override
IPen createPen() {
return new UpPen();
}
@Override
IPencil createPencil() {
return new UpPencil();
}
}
//定义低档工厂实现
class DnFactory extends IFactory{
@Override
IPen createPen() {
return new DnPen();
}
@Override
IPencil createPencil() {
return new DnPencil();
}
}
(1)抽象工厂的创建步骤
创建产品接口,如 IPen,IPencil
创建具体的产品子类,如UpPen,DnPen,UpPencil,DnPencil
创建抽象工厂,如IFactory,用于穿件产品对象
创建具体的工厂实现类,如Upfactory,DnFactory
6、什么是代理模式?代理模式有哪几种实现方式? 动态代理的实现方式(jdk、cglib),这两种实现方式各有什么不同?
什么是代理模式?(设计模式—代理模式)
代理模式:在调用处不直接调用目标类进行操作,而是调用代理类,然后通过代理类来调用目标类进行操作。在代理类调用目标类的前后可以添加一些预处理和后处理操作来完成一些不属于目标类的功能。
为什么要使用代理模式?
通过代理模式可以实现对目标类调用的控制、在目标类调用前/后进行一些不属于目标类的操作,如:数据验证、预处理、后处理、异常处理等
代理模式的几种实现方式:静态代理和动态代理
摘抄地址
什么是静态代理什么是动态代理?
静态代理:代理类只能实现对”特定接口的实现类“进行代理
动态代理:代理类可以实现对多种类的代理
jdk代理和cglib代理区别在哪里?
jdk动态代理:代理所有“实现的有接口”的目标类
cglib动态代理:代理任意一个目标类,但对final类和方法无法代理
不同点:jdk动态代理的目标类必须实现的有接口,因为在调用Proxy.newProxyInstance()的时候需要传入目标类的接口类。而cglib不做此限制
摘抄地址
7、什么spring框架?用过spring框架么?
Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
◆目的:解决企业应用开发的复杂性
◆功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
◆范围:任何Java应用
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
8、什么是IOC和DI?说说你对IOC和DI的理解?
1、IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
用图例说明一下,传统程序设计如下图1,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2所示
2、IoC能做什么
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
3、IoC和DI
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
(二)分享另一位大神(没找到源链接)对IoC与DI浅显易懂的讲解
1、IoC(控制反转)
首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看看找到自己喜欢的,然后打听她们的兴趣爱好、qq号、电话号………,想办法认识她们,投其所好送其所要……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个),使用完之后还要将对象销毁,对象始终会和其他的接口或类耦合起来。
那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个要求的列表,告诉它我想找个什么样的女朋友,然后婚介就会按照我们的要求,提供一个女孩,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
2、DI(依赖注入)
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
摘抄地址
DI是依赖注入:
spring容器会管理托管资spring容器中的bean之间的依赖关系(bean与bean之间的一种组合关系),spring在创建一个bean的实例对象时,会根据该bean对象与其他的bean对象之间的组合关系,依次实例化相关的bean,然后注入给生成的目标bean对象,最终创建完成目标bean对象;
IOC是反转控制:
spring容器中托管的bean与bean之间具有依赖关系(组合关系),如果没有spring容器,在创建目标bean时,需要先根据组合关系自主创建被依赖的bean对象,然后才能实例化目标bean对象;在spring容器中,将需要自主创建被依赖的bean对象的权利交给spring容器来完成,这就是对自主创建依赖的bean对象的控制权限的反转,反转给spring容器;
9、适配器模式和装饰模式,区别?
对适配器模式的功能很好理解,就是把一个类的接口变换成客户端所能接受的另一种接口,从而使两个接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式的结构:
target(目标接口):所要转换的所期待的接口
Adaptee(源角色):需要适配的类
Adapter(适配器):将源角色适配成目标接口,一般持有源接口的引用(或者继承源接口),且实现目标接口。
Java I/O中的适配模式
适配器的作用就是将一个接口适配到另一个接口。在 JAVA的IO类库中有很多这样的需求,如将字符串数据转变成字节数据保存到文件中,将字节数据转变成流数据等。下面以InputStreamReader和OutputStreamWriter 类为例介绍适配器模式。
InputStreamReader 和 OutputStreamWriter 分别继承Reader和Writer两个抽象类,但是要创建它们的对象必须在构造函数中传入一个 InputStream和 OutputStream 的实例。InputStreamReader 和 OutputStreamWriter
也就是将 InputStream和 OutputStream适配到Reader和Writer。
InputStreamReader的类结构如下所示:
InputStreamReader继承了Reader抽象类并实现,且持有了InputStream的引用,这里是通过StreamDecoder类间接持有的,因为从byte到char要经过编码。
很显然,适配器就是InputStreamReader,源角色就是InputStream代表的实例对象,目标接口就是Reader类。OutputStreamWriter 也类似。
在I.O类库中还有很多类似的用法,如StringReader将一个string类适配到Reader接口,ByteArrayInputStream适配器将byte数组适配到InputStream流接口处理。
装饰者模式
装饰着模式,顾名思义,就是将某个类重新装扮一下,使得它比原来更“漂亮”,或者在功能上更强大,这就是装饰器模式所要达到的目的。但是作为原来的这个类的使用者还不应该感受到装饰前与装饰后有什么不同,即用法不变,否则就破坏了原有类的结构了,所以装饰器模式要做到对被装饰类的使用者透明,这是对装饰器模式的一个基本要求。
装饰器模式的结构
component : 抽象组件角色,定义一组抽象的接口,规定这个被装饰组件都有哪些功能
concreteComponent:实现这个抽象组件的所有功能。
Decorator:装饰器角色,它持有一个component对象实例的引用,定义一个与抽象组件一致的接口
ConcreteDecorator:具体的装饰器实现者,负责实现装饰器角色定义的功能。
JAVA IO 中的装饰器模式
前面介绍了装饰器模式的作用就是赋予被装饰的类更多功能,在java I/O 类库中有很多不同的功能组合情况,这些不同的功能组合都是使用了装饰器模式事项大的,下面以FilterInputStream为例介绍装饰器模式的使用。
下图是FilterInputStream的类结构图:
InputeStream 类就是以抽象组件存在的:而FileInputStream就是具体组件,它实现了抽象组件的所有接口;FilterInputStream类无疑就是装饰角色,它实现了InputStream类的所有接口,并且持有InputStream的对象实例的引用;
BufferedInputStream是具体的装饰器实现者,它给InputStream类附加了功能,这个装饰器类的作用就是使得InputStream读取的数据保存在内存中,而提高读取的性能。与这个装饰器类有类似功能的还有LineNumberInputStream(java 1.8 已经过期)类,它的作用就是提高行按行读取数据的功能,它们都是InputStream类增强了功能,或者提升了性能。
适配器模式与装饰器模式的区别
装饰器与适配器都有一个别名叫做 包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用,但是使用它们的目的很不一一样。适配器模式的意义是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。
而装饰器模式不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的。