一、职责型模式介绍:
1.普通的对象需要一些独立操作的信息和方法。然而,有时却需要将对象从一般的独立性操作中分离出来,以便集中职责。
很多设计模式都能满足这一需求。有的模式则通过引入对象来封装这些请求,并将该对象从依赖于它的其他对象中分离出来。
面向职责的模式提供了用于集中、加强以及限制普通对象责任的技术。
2.常规的职责型模式:
一个易于使用的类的特征在于,它的方法名是有意义的,并能准确地表述方法要做的事情。
在面向对象的系统中,职责的合理分配所建立的原则,似乎促进了计算机科学更进一步的成熟。在一个系统中,如果每个类
和方法都清晰定义了它的职责,并能正确使用它们,这个系统就是迄今为止所能见到最为健壮的系统。
3.根据可见性控制职责:
类和方法承担着各种各样的职责,Java可以限制类、字段和方法的可见性,从而去限制其他开发人员对开发的代码的
调用。
4.超越普通职责:
无论一个类如何限制它的成员,面向对象开发通常都会将职责分散到各个独立的对象中。换句话说,面向对象开发促进
了封装,封装是指对象基于自己的数据进行操作。
职责分离是一种规范的做法,但一些设计模式却反对这种规范,并且将职责转移到中间对象或者中心对象。
单例模式:将职责集中到某个类的单个实例中;
观察者模式:将对象从依赖于它的对象中解耦;
调停者模式:将职责集中在某个类,该类可以监督其他对象的交互;
代理模式:让一个对象扮演其他对象的行为;
职责链模式:允许将请求传递给职责链的其他对象,直到这个请求被某个对象处理;
享元模式:将共享的、细粒度的对象职责集中管理。
二、单例(Singleton)模式:
通常,对象通过在自身属性上执行任务来承担自己的职责,除了需要维护自身的一致性外,无须承担其他任何职责。
在某些场景,需要找到承担职责的对象,并且这个对象是它所属类的唯一实例。这样就可以使用单例模式。
单例模式的意图是为了确保一个类有且仅有一个实例,并为它提供一个全局访问点。
1.单例模式机制:
为了避免多个实例化该类,可以创建唯一一个构造函数,并将其设置为私有的访问权限。注意,如果创建了其他非私有的构造
函数,或者没有创建任何构造函数,其他对象都能够实例化该类。
创建一个担当着独一无二角色的对象,有多种方式。但是,不管你如何创建一个单例对象,都必须确保其他开发人员不能
创建该单例对象的新的实例。
2.单例模式的写法:
第一种写法(懒汉,线程不安全):这种写法lazy loading很明显,但是致命的是在多线程不能正常工作
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第三种写法(饿汉):基于classloader机制避免了多线程的同步问题,在instance在类装载时就实例化,虽然导致类
装载的原因有很多,在单例模式中大多数都是调用getInstance方法,但是也不能有其他的方式(或者其他的静态方法)导致装
载,这时初始化instance没有达到lazy loading的效果。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
第四种写法(饿汉,变种):在类初始化即实例化instance
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
第五种写法(静态内部类):利用了classloader的机制来保证初始化instance时只有一个线程,跟第三和第四不同是:
第三和第四是只要Singleton类被装载了,那么instance就会被实例化,而这种方式是Singleton不一定被初始化。因为
SingletonHolder类没有被主动使用,只有现实通过调用getInstance方法时,才会现实装载SingletonHolder类,从而实例化。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
第六种写法(枚举):这种方式是Effective Java作者提倡的方式,它不仅避免多线程同步问题,而且还能防止反序列
化重新创建新的对象,jdk1.5才加入enum特性
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
第七种写法(双重校验锁):
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
3.单例和线程:
在多线程环境下延迟初始化一个单例模型,必须避免多个线程同时初始化该单例对象。在多线程环境下,无法保证在
其他线程开始执行该方法时,当前线程完整地执行完该方法。这可能出现两个线程同时初始化一个单例对象的情况。
4.小结:
单例模式保证类仅有一个实例,并为其提供了一个全局访问点。通过延迟初始化(仅在第一次使用时才初始化),一个
单例对象是达到此目的的通用做法。对象具有唯一性,并不意味着使用了单例模式。单例模式通过隐藏构造函数,提供对象创建
的唯一入口点,从而将类的职责集中在类的单个实例中。
三、观察者(Observer)模式:
客户端常用调用它所感兴趣的对象的方法来获取信息。然而,一旦感兴趣的对象发生改变就会出现问题:客户端怎样才能直到
它所依赖信息发生了改变?
当客户端感兴趣的对象某个方面发生了改变时,需要创建一个对象负责通知客户端。问题是,由于客户端自己知道它所感兴趣
的某些方面,因此这个对象自身却不应该承担更新客户端的职责。一个解决方案是当对象发生改变时通知客户端,让客户端自己去
查询对象的新状态。
观察者模式的意图是在多个对象之间定义一对多的依赖关系,当一个对象的状态发生改变时,会通知依赖于它的对象,并根据
新状态做出相应的反应。
1.观察者模式中的角色:
抽象被观察者角色:把所有的对观察者对象的引用保存在一个集合中,每个被观察者角色都可以有任意数量的观察者。
被观察者提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
public interface Watched
{
public void addWatcher(Watcher watcher); //添加观察者
public void removeWatcher(Watcher watcher); //移除观察者
public void notifyWatchers(); //当Watcher方法改变时,这个方法被调用,通知所有的观察者
}
抽象观察者角色:为所有具体的观察者者定义一个接口,在得到主题的通知时更新自己。
public interface Watcher
{
public void update();
}
具体观察者角色:该角色实现抽象观察者所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类
实现。如需要,具体观察这角色可以保存一个指向具体主题角色的引用。
public class Security implements Watcher
{
@Override
public void update()
{
System.out.println(“运输车有行动,保安贴身保护");
}
}
具体被观察者角色:在被观察者内部状态改变时,给所有登记过的观察者发出通知,具体被观察者角色通常用一个
子类实现。
public class Transporter implements Watched
{
private List<Watcher> list = new ArrayList<Watcher>();
@Override
public void addWatcher(Watcher watcher)
{
list.add(watcher);
}
@Override
public void removeWatcher(Watcher watcher)
{
list.remove(watcher);
}
@Override
public void notifyWatchers(String str)
{
for (Watcher watcher : list)
{
watcher.update();
}
}
}
当需要观察者要知晓被观察者内部的数据变化时,只需在被观察者内部注册观察者的对象即可addWatcher(Watcher
watcher)。当被观察者内部数据发生变化时被观察者操作notifyWatchers(String str)就把改变的数据推送给注册后的观察者。
2.适用场景:
1).当一个抽象模型有两个方面,其中一个方面依赖另一个方面。将者二者封装在独立的对象中以使它们各自独立地改变
和复用。
2).当一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
3).当一个对象必须通知其他对象,而它又不能假定其它对象是谁,换言之,不希望这些对象是紧密耦合的。
3.维护Observer对象:
有时我们可能无法创建一个观察类,该类继承自observable类的子类。特别是当该类已经是其他(不是Java内置的
object类)的子类时,我们可以提供一个拥有observable对象的类,该类可以将关键的方法调用转发给observable对象。
Java.awt包中component类就使用了这种方法,但是它用了一个PropertyChangeSupport对象代替了Observable对象。
使用observer也好,使用PropertyChangeSupport也好或者其他类也罢,构建观察者模式的要点是要在对象之间建立起一个一
多的关系。当一个对象状态发生改变时,所有依赖它的对象都会被通知,并做出相应的更新。这有助于缩小职责范围,使得维护
观察者和被观察者对象变得轻而易举。
四、调停者(Mediator)模式:
面向对象开发要求尽可能恰当地分配职责,要求对象能够独立地完成自己的任务。如观察者模式,就是通过最小化对象与
对象之间的职责交互,从而支持职责的合理分配。单例模式是将职责集中在某个对象中以便其他对象的访问与重用。与单例模式
相似,调停者模式也是集中职责,但它是针对一组特殊的对象,而不是系统中全部对象。
当对象间的交互趋向复杂,而每个对象都需要知道其他对象的情况时,提供一个集中的控制权时很有用的。当相关对象的
交互逻辑独立于对象的其他行为时,职责的集中同样有用。
调停者模式的意图是定义一个对象,封装一组对象的交互,从而降低对象间的耦合度,避免了对象间的显示引用,并且可以
独立地改变对象的行为。
1.GUI调停者:
GUI组件对调停者模式的运用可谓水到渠成,当事件发生时,它会通知调停者而不是直接更新其他组件。GUI应用可能
是最常见的使用调停者模式的程序了。但在其他场景下,可能需要引入调停者模式。
无论何时,只要对象之间存在复杂的交互行为,就可以将这些交互职责集中到这些对象之外的一个调停者对象中。这就
形成了松耦合--减少了对象间的直接交互。在一个独立类中,管理对象间的交互还能简化与标准化对象间的交互规则。如当需要
管理关系的一致性时,可以使用调停者模式。
2.调停者的实例运用:
想象这样一个场景:一个系统内部通过许多的类互相调用来完成一系列的功能,这个系统内部的每个类都会存在至少一次
的调用与被调用,这种情况下,一旦某个类发生问题,进行修改,无疑会影响到所有调用它的类,甚至它调用的类,可见这种情况
类与类之间的耦合性极高(体现为太多的复杂的直接引用)。
这正是调停者模式的主场,调停者犹如第三方中介一般,将所有的类与类之间的引用都导向调停者类,所有类的请求,
一致发向调停者,由调停者再发向目标类,这样原本复杂的网状的类关系,变成了简单的星型类关系,调停者类位于核心,所有
其他类位于外围,指向调停者。如此这般,类与类之间的调用耦合被解除(通过统一的第三方来发起调用),某个类发生问题,
发生修改,也只会影响到调停者,而不会直接影响到简介发起调用的那些类。
生活中的例子:一个公司部门,有一个经理来充当调停者,余下的员工充当互相作用的类。
抽象调停者(Mediator)角色:定义出员工对象到经理对象的接口,其中主要方法时一个(多个)事件方法。
/** * 调停者接口 */ public interface Mediator { void change(String message,Zhiyuan zhiyuan,String name); }
具体调停者(ConcreateMediator)角色:实现了抽象调停者所有声明的事件方法,具体调停者知晓所有员工类,并负责
具体的协调各员工对象的交互关系。
/** * 调停者--经理 */ public class JingLi implements Mediator { @Override public void change(String message, Zhiyuan zhiyuan, String name) { } }
抽象员工类(Colleague)角色:定义出调停者到员对象那个的接口,员工对象只知道调停者而不知道其余员工对象。
/** * 职员的接口 */ public abstract class Zhiyuan { String name; private Mediator mMediator; public Zhiyuan(Mediator mediator,String name) { this.mMediator = mediator; this.name = name; } /** * 被调停者调用的方法 * @param message * @param name */ public void called(String message,String name) { } public void call(String message,Zhiyuan zhiyuan,String name) { mMediator.change(message,zhiyuan,name); } }
具体员工类(ConcreateColleague)角色:所有的具体的员工类均从抽象同事类继承而来。实现自己的业务,在需要要
与其他员工通信时,就与持有调停者通信,调停者会负责与其他的同事交互。
/** * 职员A */ public class Zhiyuan1 extends Zhiyuan { public Zhiyuan1(Mediator mediator, String name) { super(mediator, name); } }
五、代理(Proxy)模式:
普通对象可以通过公共接口完成自己所需完成工作。然而,有些对象却由于某些原因无法履行自己日常的职责。如有的对象
加载时间过长,有的对象运行在其他的计算机上,或者需要拦截发送到对象的消息。对于这些场景,我们可以引入代理对象,通过
客户端需要的职责,并将相应的请求转发给底层的目标。
代理模式的意图是通过提供一个代理(Proxy)或者占位符来控制对该对象的访问。
1.静态代理:
RealSubject:委托类,Proxy是代理类;
Subject:委托类和代理的接口;
request:委托类和代理类的共同方法。
interface Subject {
void request();
}
class RealSubject implements Subject {
public void request(){
System.out.println("RealSubject");
}
}
class Proxy implements Subject {
private Subject subject;
public Proxy(Subject subject){
this.subject = subject;
}
public void request(){
System.out.println("begin");
subject.request();
System.out.println("end");
}
}
public class ProxyTest {
public static void main(String args[]) {
RealSubject subject = new RealSubject();
Proxy p = new Proxy(subject);
p.request();
}
}
2.动态代理:
动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托
类的方法进行统一处理。
定义业务逻辑:
public interface Service {
//目标方法
public abstract void add();
}
public class UserServiceImpl implements Service {
public void add() {
System.out.println("This is add service");
}
}
利用java.lang.reflect.Proxy类和java.lang.reflect.InvoactionHandler接口定义代理类的实现:
class MyInvocatioHandler implements InvocationHandler {
private Object target;
public MyInvocatioHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----before-----");
Object result = method.invoke(target, args);
System.out.println("-----end-----");
return result;
}
// 生成代理对象
public Object getProxy() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(loader, interfaces, this);
}
}
使用动态代理类:
public class ProxyTest {
public static void main(String[] args) {
Service service = new UserServiceImpl();
MyInvocatioHandler handler = new MyInvocatioHandler(service);
Service serviceProxy = (Service)handler.getProxy();
serviceProxy.add();
}
}
面向对象的开发中往往力求对象之间保持松散耦合,确保对象各自的责任具体并能最小化。这样的设计可以使得系统更
加容易修改,同时降低产生缺陷的风险。从某种角度上讲,Java语言有利于做出解耦的设计。客户端通常只能访问对象可见的接
口,而不了解其实现细节。同时,客户端只需要知道哪个对象具有它所需要调用的方法即可。当将若干对象按照某种层次结构进
行组织时,客户端可能事先并不了解应该使用哪个类。这些对象要么执行该方法,要么请求传递给下一个对象。
责任链模式的目的是通过给予多个对象处理请求的机会,以解除请求的发送者与接收者之间的耦合。意图在于减轻调用
压力,使它们无须了解哪个对象可以处理调用请求。
1.定义:
使多个对下岗都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连城一条链,并沿着这
条链传递该请求,直到有一个对象处理他为止。
2.角色定义及实例演示:
抽象处理者角色(Handler):定义出一个处理请求的接口。如需要,皆苦可以定义出一个方法以设定和返回对下家的引
用。这个角色通常由一个Java抽象类或Java接口实现。
public abstract class Handler {
/**
* 持有后继的责任对象
*/
protected Handler successor;
/**
* 示意处理请求的方法,虽然这个示意方法是没有传入参数的
* 但实际是可以传入参数的,根据具体需要来选择是否传递参数
*/
public abstract void handleRequest();
/**
* 取值方法
*/
public Handler getSuccessor() {
return successor;
}
/**
* 赋值方法,设置后继的责任对象
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
}
具体处理者角色(ConcreateHandler):具体处理者接收到请求后,可以选择将请求处理掉,或者将请求传给下家。由
于具体处理者持有对下家的引用,因此,如需要具体处理者可以访问下家。
public class ConcreteHandler extends Handler {
/**
* 处理方法,调用此方法处理请求
*/
@Override
public void handleRequest() {
/**
* 判断是否有后继的责任对象
* 如果有,就转发请求给后继的责任对象
* 如果没有,则处理请求
*/
if(getSuccessor() != null)
{
System.out.println("放过请求");
getSuccessor().handleRequest();
}else
{
System.out.println("处理请求");
}
}
}
客户端的使用:
public class Client {
public static void main(String[] args) {
//组装责任链
Handler handler1 = new ConcreteHandler();
Handler handler2 = new ConcreteHandler();
handler1.setSuccessor(handler2);
//提交请求
handler1.handleRequest();
}
}
3.小结:
在运用职责链时,客户端不必事先知道对象集合中哪个对象可提供自己需要的服务。当客户端发出调用请求后,该
请求会沿着职责链转发请求,直到找到提供该服务的对象为止。这就可以降低客户端与提供服务的对象之间的耦合度。
如果某个对象链能够应用一系列不同的策略某个问题,如解析用户的输入,这时也可应用职责链模式。该模式更
常见于组合结构,它具有一个包容的层次结构,为对象链提供了一种自然的查询顺序。简化对象链和客户端的代码是职责链的一
个主要优点。
七、享元(Flyweight)模式:
享元模式在客户对象间提供共享对象,并且为共享对象创建职责,以便普通对象不需要考虑共享对象创建的问题。通常情况下
任何时候都只能有一个客户对象引用该共享对象。当某个客户对象改变该共享对象的状态时,该共享对象不需要通知其他客户对象
然而有时候可能需要让多个客户对象同时共享访问某个对象。
享元模式的意图是通过共享有效地支持大量细粒度的对象。
1.不变性:
享元模式让对个客户对象间共享访问限定数量的对象。为了实现这个目的,你必须要考虑到当某个对象改变了该共享对像
的状态时,该状态变化会影响到每个访问它的对象。当多个客户对象要共享访问某个对象时,若要保证对象间不会互相影响,一
种最简单而又常用的做法是,限制客户对象调用任何可能改变共享对象的方法。可以通过将对象设置为不变(immutable)类型
来达到这一目的。然而,这样做会导致对象在创建后无法改变。Java中最常见的不可变对象String类。当创建一个String对象后
无论你还是其他客户对象都无法改变该对象的字符。
若存在大量相似对象,而可能需要共享访问它们,但它们可能并非一成不变。在这种情况下,使用享元模式的第一步是
对象中不可变得部分抽取出来,共享它们。
2.享元模式的解析及实例示例:
在Flyweight模式中,由于要产生各种各样的对象,所以在Flyweight(享元)模式中常出现Factory模式。Flyweight的
内部状态是用来共享的,Flyweight factory负责维护一个对象池(Flyweight Pool)l来存放内部状态的对象。Flyweight模式是
一个提高程序效率和性能的模式,会大大加快程序的运行速度。
定义一个抽象Flyweight类:
package Flyweight;
public abstract class Flyweight{
public abstract void operation();
}
实现具体类:
public class ConcreteFlyweight extends Flyweight{
private String string;
public ConcreteFlyweight(String str){
string = str;
}
public void operation()
{
System.out.println("Concrete---Flyweight : " + string);
}
}
实现工厂方法类:在1处定义Hashtable用来储存各个对象;在2处选出要实例化的对象,在6处将对象返回,如在
Hashtable中没有要选择的对象,此时变量flywiget为null,产生一个新的flyweight储存在Hashtable中,并将对象返回。
public class FlyweightFactory{
private Hashtable flyweights = new Hashtable();//----------------------------1
public FlyweightFactory(){}
public Flyweight getFlyWeight(Object obj){
Flyweight flyweight = (Flyweight) flyweights.get(obj);//----------------2
if(flyweight == null){//---------------------------------------------------3
//产生新的ConcreteFlyweight
flyweight = new ConcreteFlyweight((String)obj);
flyweights.put(obj, flyweight);//--------------------------------------5
}
return flyweight;//---------------------------------------------------------6
}
public int getFlyweightSize(){
return flyweights.size();
}
}
3.小结:
享元模式可以使共享地访问那些大量出现的细粒度对象,享元对象必须是不可变得,可以将那些需要共享访问,并且
不变得部分提取出来。为了确保享元对象能够被共享,需要提供并强制客户对象使用享元工厂来查找享元对象。访问修饰符对其他
开发者进行了一定的限制,但是内部类的使用使限制更进一步,完全限制了类仅能由其他外部容器访问,在确保客户对象正确地
使用享元工厂后,就可以提供大量细粒度对象的安全共享访问了。