设计模式-- from 大话设计模式 & HeadFirst设计模式
第一部分:接口型模式
引入
在系统的设计时常常遇到这样的问题:类Client的实例instanceClient希望使用另一个对象instanceX提供的服务service,但是设计时,我们并不能确定对象instanceX究竟属于哪个类。
这种情况常见的解决办法是,将对象instanceX提供的服务service抽象为一个接口ServiceProvider,然后让对象instanceClient通过持有接口ServiceProvider的实例来使用服务service。这种通过接口简介获得服务的解决方案就是接口模式。
接口模式包括:适配器模式、外观模式、合成模式和桥接模式等。
ISP
Interface Segregation Principle,接口隔离原则,面向对象设计中的一个核心原则。
接口和委托的区别
接口可以包含属性、索引、方法以及事件,是约束类应该具备的功能集合,约束了类应该具备的功能,使类从千变万化的具体逻辑中解脱出来;委托不能包含事件,是约束方法集合的一个类,可以便捷地使用委托对这个方法集合进行操作,多用于事件处理中。
一、适配器模式
描述
把一个类的接口转换成客户端所期待的另一种接口,从而使原接口不匹配而无法在一起工作的两个类能在一起工作。
类适配器
通过继承来实现
public interface XmlReader{
public InputStream xlmReader();
}
public interface ReaderXml{
public InputStream readerXml();
}
public static class A implements XmlReader{
@Override
public InputStream xlmReader() {
System.out.println("方法A-xmlReader");
return null;
}
}
public static class B implements ReaderXml{
@Override
public InputStream readerXml() {
System.out.println("方法B-readerXml");
return null;
}
}
public static class Adapter extends B implements XmlReader{
@Override
public InputStream xlmReader() {
return readerXml();
}
}
public static void main(String[] args){
Adapter adapter = new Adapter();
adapter.xlmReader();
}
对象适配器
通过组合方式实现
public static class Adapter implements XmlReader{
private B b;
public Adapter(B b){
this.b = b;
}
@Override
public InputStream xlmReader() {
return b.readerXml();
}
}
public static void main(String[] args){
B b = new B();
Adapter adapter = new Adapter(b);
adapter.xlmReader();
}
适配器模式的几个要素
目标(Target):定义一个客户端使用的特定接口。如代码中的XmlReader。
客户(Client):使用目标接口,与和目标接口一致的对象合作。如代码中的main方法。
被适配者(Adaptee):一个现存需要匹配的接口。如代码中的B。
适配器(Adapter):负责将Adaptee的接口转换成Target的接口。如代码中的Adapter。
二、外观模式
描述
外观模式定义了一个将子系统的一组接口继承在一起的高层接口,以提供一个一致的界面,其他系统可以方便地调用子系统中的功能,而忽略子系统内部发生的变化。
使用场合
- 为一个比较复杂的子系统提供一个简单的接口
- 将客户程序与子系统的实现部分分离,提高子系统的独立性和可移植性
- 简化子系统间的依赖关系
注意事项
- 设计外观时,不需要增加额外的功能
- 不要从外观方法中返回子系统中的组件给客户。
- 应用外观的目的是提供一个高层次的接口,因此外观方法最适合提供特定的高层次的业务服务,而不是进行低层次的单独的业务执行。
三、组合模式
描述
又称‘部分-整体’模式,将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
实现
组合部件(Component):它是一个抽象角色,为要组合的对象提供统一的接口。
叶子节点(Leaf):在组合中表示叶子节点对象,无子节点。
合成部件-枝节点(Composite):定义有枝节点的行为,用来存储部件。
组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。
适用场景
- 当需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象不同时,统一地适用组合结构中的所有对象时。
引申
组合模式的透明方式:在Component中声明所有子类都具备了Add和Remove方法,好处在于叶节点和枝节点对于外界没有区别,它们具备完全一致的行为接口,但是问题在于,Leaf类本身不具备Add()、Remove()方法功能,所有实现起来没有意义。
组合模式的安全模式:在Component接口中不去声明Add和Remove方法,那么子类的Leaf也不需要去实现它,而是在Composite声明所有用来管理子类对象的方法。不过由于不透明,所以树叶和树枝将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。
四、桥接模式
描述
桥接器模式,又称为桥梁模式,是为了实现抽象部分与实现部分脱耦,使它们各自可以独立地变化。
特点
- 分离接口及其实现部分,实现了AbstractIon和Implementor的分离,有助于降低对实现部分的依赖性,从而产生更好的结构化系统。
- 提高了可扩充性,可以独立的对AbstractIon和Implementor层次结构进行扩充。
与适配器的区别
- 共同点:都是让两个东西配合工作
- 不同点:适配器是改变已有的两个接口,让他们相融,桥接模式是分离抽象化和实现,使两者的接口可以不同,目的是分离。
适用情况
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系
- 设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是安全透明的。
- 一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
五、责任链模式
描述
在责任链模式里,很多的对象由每一个对象对其下家的引用而联接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
设计角色
- 抽象处理者角色:定义出一个处理请求的接口,加入需要,接口可以定义出一个方法,以返回对下家的引用。
- 具体处理者角色:接到请求后,可以选择将请求处理掉,或者将请求传给下家。
- 客户端:发送请求。
纯与不纯的责任链模式
- 纯的责任链模式:要求一个具体的处理者对象只能在两个行为中选择一个,一是承担责任,二是把责任推给下家。不应该出现某一个具体处理者对象在承担了不部分责任后又把责任向下传的情况,在一个纯的责任链模式里,一个请求必须被某一个处理者对象所接受。
- 不纯的责任链模式:一个请求可以最终不被任何接受端对象所接受。
适用情况
- 系统已经有一个由处理者对象组成的链,这个链可能由复合模式给出。
- 当有对于一个的处理者对象会处理一个请求,而且事先并不知道到底由哪一个处理者对象处理一个请求,这个处理者对象是动态确定的。
- 当系统想发出一个请求给多个处理者对象中的某一个,但是不明显指出是哪一个处理者对象会处理此请求
- 当处理一个请求的处理者对象集合需要动态地指定时。
优劣
优点:
- 降低了发出命令的对象和处理命令的对象之间的耦合。
- 可以动态地改变责任链
- 让各个处理者专注于实现自己的职责
缺点: - 推卸责任可能导致延迟。
六、单体模式(单例模式)
描述
对象只要利用自身的属性完成了自己的任务,那该对象就是承担了责任。除了维持自身的一致性,该对象无需承担其他任何责任。如果该对象还承担着其他责任,而其他对象又依赖于该特定对象所承担的责任,我们就需要得到该特定对象。
目的
将类的责任集中到唯一的单体对象中,确保该类只有一个实例,并且为该类提供一个全局访问点。
代码实现
代码实现借鉴了一片blog,写得很不错,就直接用了,这里给出链接: java单例模式
单线程环境下的实现
1、饿汉方式
public class Singleton {
//主动创建指向自己实例的静态引用
private static Singleton singleton = new Singleton();
//私有构造方法,防止别的程序创建对象
private Singleton(){
}
//获取唯一实例
public static Singleton getInstance(){
return singleton;
}
}
2、懒汉方式
public class Singleton {
//指向自己实例的静态引用
private static Singleton singleton;
//私有构造方法,防止别的程序创建对象
private Singleton(){
}
//获取唯一实例
public static Singleton getInstance(){
//需要使用时才会创建
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
多线程环境下的实现
1、饿汉方式
饿汉方式是线程安全的。
2、懒汉模式
- 同步延迟加载 -synchronized方法 --由于同步块的作用域比较大,锁的粒度有点粗,所以效率低。
public class Singleton {
//指向自己实例的静态引用
private static Singleton singleton;
//私有构造方法,防止别的程序创建对象
private Singleton(){
}
//获取唯一实例 使用 synchronized修饰,临界资源的同步互斥访问
public static synchronized Singleton getInstance(){
//需要使用时才会创建
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
- 同步延迟加载 -synchronized块 --效率仍然比较低。
public class Singleton {
//指向自己实例的静态引用
private static Singleton singleton;
//私有构造方法,防止别的程序创建对象
private Singleton(){
}
//获取唯一实例
public static Singleton getInstance(){
//需要使用时才会创建, 使用 synchronized 块,临界资源的同步互斥访问
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
}
- 同步延迟加载 -使用内部类实现延迟加载 --效率比较高的一种方式
public class Singleton {
//私有构造方法,防止别的程序创建对象
private Singleton(){
}
// 私有内部类,按需加载
public static class Holder{
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return Holder.singleton;
}
}
3、单例模式与双重检查
使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率。
public class Singleton {
//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
private static volatile Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
//只需在第一次创建实例时才同步
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
4、单例模式与threadLocal
借助于ThreadLocal,可以实现双重检查模式的变体。我们将临界资源线程局部化,具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为 线程局部范围内的操作 。这里的 ThreadLocal 也只是用作标识而已,用来标识每个线程是否已访问过:如果访问过,则不再需要走同步块,这样就提高了一定的效率。
与直接双重检查模式使用相比,使用ThreadLocal的实现在效率上还不如双重检查锁定。
public class Singleton {
//ThreadLocal 线程局部变量
private static ThreadLocal<Singleton> threadLocal = new ThreadLocal<Singleton>();
private static Singleton singleton = null;
private Singleton(){
}
public static Singleton getInstance(){
if(threadLocal.get() == null){
createInstance();
}
return singleton;
}
public static void createInstance(){
synchronized (Singleton.class){
//只需在第一次创建实例时才同步
if(singleton == null){
singleton = new Singleton();
}
}
threadLocal.set(singleton);
}
}