Hi 壹——设计原则1:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。
贰——设计原则2:
针对接口编程,而不是针对实现编程。
所谓“针对接口编程”的真正含义是“针对超类型编程”。更明确点说:变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类锁产生的对象,都可以指定给这个变量。这也意味这,声明类时不用理会以后执行时的真正对象类型。
举个栗子:
假设有个抽象类Animal,有两个具体的实现(Dog和Cat)继承Animal。
“针对实现编程”
Dog d = new Dog();
d.bark();
而“针对接口/超类型编程”的做法会如下:
Animal animal = new Dog();
Animal.makeSound();
更棒的是,子类实例化的动作不再需要在代码中硬编程,而是在运行时才指定具体实现的对象。
a = getAnimal();
a.
makeSound();
叁——设计原则3:
多用组合,少用继承。
设计模式1:
策略模式 定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
肆——设计原则4:
为了交互对象之间的松耦合设计而努力。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
设计模式2:
观察者模式:
(JAVA 内置的观察者模式如何运作)
1.
如何把对象变成观察者?
a)
实现观察者接口(java.util.Observer),然后调用任何Observable对象的addObserver()方法。不想再当观察者时,调用deleteObserver()方法就可以了。
2.
观察者如何送出通知?
首先,你需要利用扩展java.util.Observable
接口产生“可观察者”类,然后,需要两个步骤:
a)
先调用setChanged()方法,标记状态已经改变的事实。
b)
然后调用两种notifyObservers()方法中的一个:notifyObservers()或notifyObservers(Object arg)
定义:
在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会受到通知,并自动更新
|
本节要点:
1.
观察者模式定义了对象之间一对多的关系。
2.
主题(也就是可观察者)用一个共同的接口来更新观察者。
3.
观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
4.
使用此模式时,你可以被观察者处推或拉数据,(然后,推的方式被认为更“正确”)。
5.
有多个观察者时,不应该依赖特定的通知次序。
6.
Java有多种观察者模式的实现,包括了通用的java.util.Observable。
7.
注意java.util.Observable实现上带来的一些问题(是类而非接口)。
8.
如果有必要的话,可以实现自己的Observable,这并不难,不要害怕。
设计模式3:
装饰者模式:
伍——设计原则5
类应该对扩展开放,对修改关闭。
定义:
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
|
本节要点:
1.
继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
2.
在我们的设计中,应该允许行为可以被扩展,而无需修改现有的代码。
3.
组合和委托可用于在运行时动态地加上新的行为。
4.
除了继承,装饰者模式也可以让我们扩展行为。
5.
装饰者模式以为这一群装饰者类,这些类用来包装具体组件。
6.
装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。
7.
装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
8.
你可以用无数个装饰者包装一个组件。
9.
装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
10.
装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
设计模式4:
工厂方法模式
定义:
它定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
|
1.
当只有一个ConcreteCreator的时候,工厂方法模式有什么优点?
尽管只有一个具体创建者,工厂方法模式依然很有用,因为它帮助我们将产品的“实现”从“使用”中
解耦。如果增加产品或者改变产品的实现,Creator并不会受到影响(因为Creator与任何ConcreteProduct之间都不是紧耦合)。
陸——設計原則6
依赖倒置原则(Dependency
Inversion Principle)【这里的“倒置”指的是和一般OO设计的思考方式完全相反】
要依赖抽象,不要依赖具体。
这个原则主要强调的是,不能让高层组件依赖低层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象。举个栗子,PizzaStore是个高层组件,它的行为是由比萨定义的:PizzaStore创建所有不同的比萨对象,准备、烘烤、切片、装盒;而比萨本身属于低层组件。
下面指导方针用于帮你避免在OO设计中违反依赖倒置原则:
1.
变量不可以持有具体类的引用。(如果使用new,就会持有具体类的引用。你可以改用工厂来避开这样的做法。)
2.
不要让类派生自具体类。
3.
不要覆盖基类中已实现的方法。(如果覆盖基类中已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。)
设计模式5:
抽象工厂模式
定义:
提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
|
它允许客户使用抽象的接口来创建一组相关的产品,而不需要关心实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
本节要点:
1.
所有的工厂都是用来封装对象的创建。
2.
简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类解耦。
3.
工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象。
4.
抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中。
5.
所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合。
6.
工厂方法允许类将实例化延迟到子类进行。
7.
抽象工厂创建相关的对象家族,而不需要依赖他们的具体类。
8.
依赖倒置原则,知道我们避免依赖具体类型,而要尽量依赖抽象。
9.
工厂是很有威力的技巧,帮助我们针对抽象变成,而不是要针对具象类编程。
设计模式6:
单件模式
定义:
确保一个类只有一个实例,并提供一个全局访问点。
|
有啥用呢?
有一些对象其实我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象、充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常、资源使用过量、或者是不一致的结果。
public class Singleton {
private static
Singleton uniqueInstance;
private
Singleton() {}
public static
synchronized Singleton getInstance() {
if
(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return
uniqueInstance;
}
}
|
把getInstance()变成同步(synchronized)方法,就可以轻易地解决多线程灾难,但是你必须知道,同步一个方法可能造成程序执行效率下降100倍。因此,如果将getInstance()的程序使用在频繁运行的地方,你可能就得重新考虑了。
怎么改善多线程呢?
1.
如果getInstance()的性能对应用程序不是很关键,就什么都别做。
2.
使用“急切”创建实例,而不用延迟实例化的做法
public class Singleton2 {
private static Singleton2 uniqueInstance = new Singleton2();
private Singleton2() {}
public static synchronized Singleton2
getInstance() {
return uniqueInstance;
}
}
|
利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单件实例
3.
用“双重检查加锁”,在getInstance()中减少使用同步。
首先检查实例,如果尚未创建,才进入同步。这样一来,只有第一次会同步。
Volatile 关键字确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量
public class Singleton3 {
private volatile static Singleton3 uniqueInstance;
private Singleton3() {}
public static synchronized Singleton3
getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton3.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton3();
}
}
}
return uniqueInstance;
}
}
|
需要注意的是:双重检查加锁不适用于1.4及更早版本的JAVA。
那么我们就结合实际的情况来分析一下二者的区别,为了说明方便,以上两种情况分别简称为
“双重检查锁”和“单重检查锁”。
1、第一次访问Instance,同时来了10个线程。对于双重检查锁,instance为null,10个线程在这里lock处排队;对于单重检查锁,10个线程在lock处排队。二者是相同的。
2、第二次、第三次。。。访问Instance,同时来了10个线程。对于双重检查锁,instance不为null,10个线程不用排队,直接返回instance;对于单重检查锁,10个线程,还必须要在lock处排队。
双重检查锁的优点体现出来了:避免了不必要的排队现象。也就是说,双重检查锁的第一重检查,是很必要的,它来保证不必要的排队。
举个例子说明,病人到医院看病,第一次去的时候,都要排队去办病历本。以后再去的时候,如果有病历本,就不用再排队去办了。
|
使用类加载器(classloader)需要注意
每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。如果这样的事情发生在单件上,就会产生多个单件并存的怪异现象。有一个解决办法:自行指定类加载器,并指定同一个类加载器。
对于全局变量和单件模式:
首先单例模式的目的是:确保类只有一个实例并提供全局访问。在Java中,全局变量基本上就是对对象的静态引用。全局变量可以提供全局访问,但是不能确保只有一个实例。全局变量也会变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间的污染。单件不鼓励这样的现象,当仍然可能被滥用。
所谓命名空间的污染,指的是程序中的变量和库中的变量同名了。
设计模式7:
命令模式
将“请求”封装成对象,以便使用
不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
|
No comments:
Post a Comment