常用设计模式简单例子
找出应用中可能需要变化之处,把它独立出来,并封装起来,不要和那些不需要变化的代码混在一起。
以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分,从而使系统变得更有弹性。
“有一个”可能比“是一个”更好,使用组合建立系统具有很大的弹性,可以在运行时动态地改变行为,只要组合的对象符合正确的接口标准即可。
针对接口编程,可以在运行时动态地改变行为,而不需要修改客户端代码,使系统更有弹性。
当两个对象松耦合,它们依然可以交互,但是不太清楚彼此的细节。
可以独立地复用其中的一个对象,并且可以改变其中一个,并不会影响另一方,只要它们之间的接口仍被遵守。
这使得系统更具弹性,能够应对变化。
目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。
遵循开闭原则,通常会引入新的抽象层次,增加代码复杂度,因此,应该在最有可能改变的地方应用,不能滥用。
跟“针对接口编程,不针对实现编程”相似,但这里更强调抽象。
不能让高层组件依赖低层组件,而且,不管高层或低层组件,都应该依赖于抽象。
从客户端代码调用框架代码,变成框架调用客户端代码。框架来定义接口,客户端来实现。
不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其他部分。
就任何对象而言,我们只应该调用属于以下范围的方法:
- 该对象本身
- 被当做方法参数传进来的对象
- 此方法创建或实例化的任何对象
- 对象的任何组件(成员变量对象)
允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎么样使用这些低层组件。
跟依赖倒置原则还是有区别的:依赖倒置原则更多是说,我们应该面向接口编程;好莱坞原则是说,低层组件将自己挂钩到系统上,由系统来主动调用。
四种类型:饿汉模式、懒汉模式(线程不安全)、双重锁懒汉模式(多线程推荐该模式)、静态内部类模式。
确保一个类只有一个实例,并提供一个全局访问点。
- Runtime:通过
Runtime.getRuntime()使用的是饿汉模式
简单工厂模式、工厂方法模式、抽象工厂模式
- 简单工厂模式:直接定义一个方法,来实例化对象。
- 工厂方法模式:定义了一个创建对象的接口,里面定义一个方法,即工厂方法,但由子类决定要实例化的是哪一个类。工厂方法让类把实例化推迟到子类。
- 抽象工厂模式:提供一个接口,里面有一系列的工厂方法,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
- 依赖倒置原则:依赖抽象,不用依赖具体类
为另一个对象提供一个替身或占位符,来控制对这个对象的访问。
- 远程代理(Remote Proxy):控制访问远程对象
- 虚拟代理(VirtualProxy):控制访问创建开销大的对象
- 保护代理(ProtectionProxy):基于权限控制对资源的访问
- 缓存代理(CachingProxy):维护之前创建的对象,在需要时返回缓存对象
- 动态代理
两种类型: 类适配器模式、对象适配器模式
将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不相容的类可以合作无间。
- 类适配器模式
动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
- InputStream作为抽象组件,FilterInputStream作为抽象装饰者。FileInputStream、ObjectInputStream等继承InputStream作为具体的组件,BufferedInputStream、LineNumberInputStream等继承FilterInputStream作为具体的装饰者。如
new BufferedInputStream(new FileInputStream())
- 开闭原则:类应该对扩展开放,对修改关闭
提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
- 最少知识原则:只和你的朋友交谈
允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
- 违背单一责任原则,要管理层次结构,还要执行叶子节点的操作;但换取到了透明性,客户可以将组合和叶节点一视同仁。这是一个折衷的案例。
在一个方法(模板方法)中定义一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
- Arrays.sort():sort()为模板方法,定义了排序算法,但里面的具体排序规则是Comparable.compareTo()方法,该方法由排序的元素去实现。(该方法不是严格意义上的模板方法模式,它没有使用子类继承,但思想一样,用一个类的方法填补模板方法中的一个方法的实现)。
- InputStream.read():该方法是由子类(如FileInputStream、ObjectInputStream等)去实现的,而这个方法又会被read(byte b[], int off, int len)模板方法调用。
- AQS锁的实现
- 好莱坞原则:别调用我们,我们会调用你
定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
ThreadPoolExecutor中的四种拒绝策略,RejectedExecutionHandler的实现类AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
将**“请求”封装成对象**,以便使用不同的请求来参数化其他对象。命令模式也可用来实现队列、日志和支持撤销的操作。
命令对象将对象和接受者包进对象中,这个对象只暴露一个execute()方法,当此方法调用时,接收者就会执行这些动作。
- OkHttp的拦截器
允许对象在内部状态改变时,改变它的行为,对象看起来好像修改了它的类。
这个模式将状态封装成独立地类,并将动作委托到代表当前状态的对象,然后行为会随着内部状态改变而改变。
- Thread的状态
- 封装变化
- 开闭原则
- 单一责任
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
Observer 与 Observable两个类。Observer类可以看作是抽象观察者角色,而Observable是抽象主题角色。
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
- 为交互对象之间的松耦合设计而努力
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
- ArrayList、HashSet的
iterator()返回的就是一个迭代器Iterator - ArrayList的
ListIterator()返回的也是一个迭代器listIterator,它比Iterator多了向前遍历的方法previous() - Vector、HashTable的
elements()返回的也是一个迭代器Enumeration
- 封装变化(封装了遍历)
- 针对接口编程,不针对实现编程
- 单一责任原则(迭代器负责遍历,聚合类负责管理对象集合)
- 包含的设计模式:观察者、组合、策略、适配器
- 观察者:Model作为被观察者,View和Controller可以对它注册观察者,Model改变时,View和Controller可以做出相应的变化或操作。
- 组合:View的组件包括了ViewGroup和View,对应组合和叶节点。
- 策略:Controller相当于View的行为,View可以根据需要,调整使用不同的策略(Controller),来实现不同的行为。
- 适配器:用来将新的Model适配成现有的Model,给现有的View和Controller使用。
- 相似点:结构上都是都是通过包装已经存在的类,来实现一些新的功能或操作。
- 不同点:主要的区别在于意图不同,装饰者不改变接口,但加入新的责任;适配器将一个接口转变成另一个接口;外观简化接口,并将客户端从子系统中解耦;代理控制对象的访问,相比适配器,代理是不改变接口的,但其中的保护代理可能只提供部分接口,这和适配器有点像。
- 相似点:结构类似,都是将某些操作延迟到子类或其他类(策略模式)中,让子类自己实现某个功能。工厂方法是模板方法的一种特殊版本。策略和模板方法都封装算法,一个用组合,一个用继承。
- 不同点:3者主要的区别在于意图不同,工厂方法由子类决定实例化哪个具体的类(产品);模板方法让子类决定如何实现算法中的某些步骤;策略封装可互换的一组算法,然后使用委托来决定采用哪一个算法。
- 相似点:类图、结构基本一样。
- 不同点:
- 意图不同:策略模式是为了解决继承带来的行为被固定的问题,通过组合不同的对象来改变context的行为;状态模式是为了解决许多条件判断的问题,通过将状态包装进状态对象中,可以在Context内改变状态对象,来改变context的行为。
- 使用方式不同:策略模式客户通常主动指定context对象要组合的策略对象是哪一个,对某一个context对象来说,通常只有一个最适合的策略对象;状态模式将一群行为封装在状态对象中,context当前状态会在状态对象集合中游走改变,context的行为也会随之改变,但context的客户对于状态对象了解不多,甚至根本是浑然不觉。




















