文章目录
- 前言
- 1.单例模式
- 单例模式的实现方式
- 2.简单工厂模式
- 3.代理模式
- 4.观察者模式
- 5.适配器模式
- 6.装饰者模式
- 总结
- 1. 单例模式:
- 2.工厂模式
- 3. 代理模式:
- 4.观察者模式
- 5.适配器模式
- 6.装饰者模式
前言
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因
1.单例模式
(1)如果一个类始终只能创建一个实例,则这个类被称为单例类,这种模式就被称为单例模式。一般建议单例模式的方法命名为:getInstance(),这个方法的返回类型肯定是单例类的类型了。getInstance方法可以有参数,这些参数可能是创建类实例所需要的参数,当然,大多数情况下是不需要的。
(2) 所谓单例设计模式简单说就是无论程序如何运行,采用单例设计模式的类(Singleton类)永远只会有一个实例化对象产生。具体实现步骤如下:
(1) 将采用单例设计模式的类的构造方法私有化(采用private修饰)。
(2) 在其内部产生该类的实例化对象,并将其封装成private static类型。
(3) 定义一个静态方法返回该类的实例。
示例代码如下:
class Singleton {
private static Singleton instance = new Singleton();// 在内部产生本类的实例化对象
public static Singleton getInstance() { // 通过静态方法返回instance对象
return instance;
}
private Singleton() { // 将构造方法封装为私有化
}
public void print() {
System.out.println("Hello World!!!");
}
}
public class SingletonDemo {
public static void main(String args[]) {
Singleton s1 = null; // 声明对象
Singleton s2 = null; // 声明对象
Singleton s3 = null; // 声明对象
s1 = Singleton.getInstance(); // 取得实例化对象
s2 = Singleton.getInstance(); // 取得实例化对象
s3 = Singleton.getInstance(); // 取得实例化对象
s1.print(); // 调用方法
s2.print(); // 调用方法
s3.print(); // 调用方法
}
}
单例模式的实现方式
两种实现方式:
1、饿汉式:在类被加载后,对象被创建,到程序结束后被释放
2、懒汉式,在第一次调用getInstance方法时,对象被创建,到程序结束后被释放
在项目中为什么要使用单例,单例有什么好处?
1、在设计一些工具类的时候(通常工具类、只有功能方法,没有属性)
2、工具类可能被频繁调用(目的是为了节省重复创建对象所带来的内存消耗,从而来提高效率)
代码如下:
/**
*
* 单例模式的实现:饿汉式,线程安全 但效率比较低
*/
public class SingletonTest {
private SingletonTest() {
}
private static final SingletonTest instance = new SingletonTest();
public static SingletonTest getInstancei() {
return instance;
}
}
/**
* 单例模式的实现:饱汉式,非线程安全
*
*/
public class SingletonTest {
private SingletonTest() {
}
private static SingletonTest instance;
public static SingletonTest getInstance() {
if (instance == null)
instance = new SingletonTest();
return instance;
}
}
/**
* 线程安全,但是效率非常低
* @author vanceinfo
*
*/
public class SingletonTest {
private SingletonTest() {
}
private static SingletonTest instance;
public static synchronized SingletonTest getInstance() {
if (instance == null)
instance = new SingletonTest();
return instance;
}
}
/**
* 线程安全 并且效率高
*
*/
public class SingletonTest {
private static SingletonTest instance;
private SingletonTest() {
}
public static SingletonTest getIstance() {
if (instance == null) {
synchronized (SingletonTest.class) {
if (instance == null) {
instance = new SingletonTest();
}
}
}
return instance;
}
}
2.简单工厂模式
简单工厂模式的核心思想就是:有一个专门的类来负责创建实例的过程。 具体来说,把产品看着是一系列的类的集合,这些类是由某个抽象类或者接口派生出来的一个对象树。而工厂类用来产生一个合适的对象来满足客户的要求。如果简单工厂模式所涉及到的具体产品之间没有共同的逻辑,那么我们就可以使用接口来扮演抽象产品的角色;如果具体产品之间有功能的逻辑或,我们就必须把这些共同的东西提取出来,放在一个抽象类中,然后让具体产品继承抽象类。为实现更好复用的目的,共同的东西总是应该抽象出来的。
/*
* 产品的抽象接口
*/
public interface Food {
/*
* 获得相应的食物
*/
public void get();
}
/*
* 麦香鸡对抽象产品接口的实现
*/
public class McChicken implements Food{
/*
* 获取一份麦香鸡
*/
public void get(){
System.out.println("我要一份麦香鸡");
}
}
public class FoodFactory {
public static Food getFood(String type) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
if(type.equalsIgnoreCase("mcchicken")) {
return McChicken.class.newInstance();
} else if(type.equalsIgnoreCase("chips")) {
return Chips.class.newInstance();
} else {
System.out.println("哎呀!找不到相应的实例化类啦!");
return null;
}
}
}
/*
* 测试客户端
*/
public class SimpleFactoryTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//实例化各种食物
Food mcChicken = FoodFactory.getFood("McChicken");
Food chips = FoodFactory.getFood("Chips");
Food eggs = FoodFactory.getFood("Eggs");
//获取食物
if(mcChicken!=null){
mcChicken.get();
}
if(chips!=null){
chips.get();
}
if(eggs!=null){
eggs.get();
}
}
}
输出结果:
3.代理模式
(1) 指由一个代理主题来操作真实主题,真实主题执行具体的业务操作,而代理主题负责其他相关业务的处理。比如生活中的通过代理访问网络,客户通过网络代理连接网络(具体业务),由代理服务器完成用户权限和访问限制等与上网相关的其他操作(相关业务)。
(2)代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)。
代码如下:
public class ImageProxy implements Image
{
//组合一个image实例,作为被代理的对象
private Image image;
//使用抽象实体来初始化代理对象
public ImageProxy(Image image)
{
this.image = image;
}
/**
* 重写Image接口的show()方法
* 该方法用于控制对被代理对象的访问,
* 并根据需要负责创建和删除被代理对象
*/
public void show()
{
//只有当真正需要调用image的show方法时才创建被代理对象
if (image == null)
{
image = new BigImage();
}
image.show();
}
}
调用时,先不创建:
Image image = new ImageProxy(null);
Hibernate默认启用延迟加载,当系统加载A实体时,A实体关联的B实体并未被加载出来,A实体所关联的B实体全部是代理对象——只有等到A实体真正需要访问B实体时,系统才会去数据库里抓取B实体所对应的记录。
借助于Java提供的Proxy和InvocationHandler,可以实现在运行时生成动态代理的功能,而动态代理对象就可以作为目标对象使用,而且增强了目标对象的功能。如:
interface Network { // 定义Network接口
public void browse(); // 定义浏览的抽象方法
}
class Real implements Network { // 真实的上网操作
public void browse() { // 覆写抽象方法
System.out.println("上网浏览信息!");
}
}
class Proxy implements Network { // 代理上网
private Network network;
public Proxy(Network network) {// 设置代理的真实操作
this.network = network; // 设置代理的子类
}
public void check() { // 身份验证操作
System.out.println("检查用户是否合法!");
}
public void browse() {
this.check(); // 调用具体的代理业务操作
this.network.browse(); // 调用真实的上网操作
}
}
public class ProxyDemo {
public static void main(String args[]) {
Network net = null; // 定义接口对象
net = new Proxy(new Real()); // 实例化代理,同时传入代理的真实操作
net.browse(); // 调用代理的上网操作
}
}
4.观察者模式
(1)对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。Android中的各种Listener就使用到了这一设计模式,只要用户对手机进行操作,对应的listener就会被通知,并作出响应的处理。
(2)所谓观察者模式,举个例子现在许多购房者都密切观察者房价的变化,当房价变化时,所有购房者都能观察到,以上的购房者属于观察者,这便是观察者模式。
java中可以借助Observable类和Observer接口轻松实现以上功能。当然此种模式的实现也不仅仅局限于采用这两个类。
import java.util.Observable;
import java.util.Observer;
class House extends Observable {
private float price;
public void setPrice(float price) {
this.setChanged();// 设置变化点
this.notifyObservers(price);// 通知所有观察者价格改变
this.price = price;
}
public float getPrice() {
return this.price;
}
public House(float price) {
this.price = price;
}
public String toString() {
return "房子价格为: " + this.price;
}
}
class HousePriceObserver implements Observer {
private String name;
public HousePriceObserver(String name) {
super();
this.name = name;
}
@Override
public void update(Observable o, Object arg) {// 只要改变了 observable 对象就调用此方法
if (arg instanceof Float) {
System.out.println(this.name + "观察的价格更改为:"
+ ((Float) arg).floatValue());
}
}
}
public class ObserDeom {
public static void main(String[] args) {
House h = new House(1000000);
HousePriceObserver hpo1 = new HousePriceObserver("购房者A");
HousePriceObserver hpo2 = new HousePriceObserver("购房者B");
HousePriceObserver hpo3 = new HousePriceObserver("购房者C");
h.addObserver(hpo1);// 给房子注册观察者
h.addObserver(hpo2);// 给房子注册观察者
h.addObserver(hpo3);// 给房子注册观察者
System.out.println(h);// 输出房子价格
// 修改房子价格,会触发update(Observable o, Object arg)方法通知购房者新的房价信息
h.setPrice(2222222);//
System.out.println(h);// 再次输出房子价格
}
}
5.适配器模式
(1)如果一个类要实现一个具有很多抽象方法的接口,但是本身只需要实现接口中的部分方法便可以达成目的,所以此时就需要一个中间的过渡类,但此过渡类又不希望直接使用,所以将此类定义为抽象类最为合适,再让以后的子类直接继承该抽象类便可选择性的覆写所需要的方法,而此抽象类便是适配器类。
(2)将两种完全不同的事物联系到一起,就像现实生活中的变压器。假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。
代码:
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
VoltageAdapter adapter = new VoltageAdapter();
phone.setAdapter(adapter);
phone.charge();
}
}
// 手机类
class Phone {
public static final int V = 220;// 正常电压220v,是一个常量
private VoltageAdapter adapter;
// 充电
public void charge() {
adapter.changeVoltage();
}
public void setAdapter(VoltageAdapter adapter) {
this.adapter = adapter;
}
}
// 变压器
class VoltageAdapter {
// 改变电压的功能
public void changeVoltage() {
System.out.println("正在充电...");
System.out.println("原始电压:" + Phone.V + "V");
System.out.println("经过变压器转换之后的电压:" + (Phone.V - 200) + "V");
}
}
6.装饰者模式
对已有的业务逻辑进一步的封装,使其增加额外的功能,如java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。
举个栗子,我想吃三明治,首先我需要一根香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包加一下,很丰盛的一顿午饭,营养又健康,那我们应该怎么来写代码呢?
首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码:
public class Food {
private String food_name;
public Food() {
}
public Food(String food_name) {
this.food_name = food_name;
}
public String make() {
return food_name;
};
}
我们写几个子类继承它:
//面包类
public class Bread extends Food {
private Food basic_food;
public Bread(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+面包";
}
}
//奶油类
public class Cream extends Food {
private Food basic_food;
public Cream(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+奶油";
}
}
//蔬菜类
public class Vegetable extends Food {
private Food basic_food;
public Vegetable(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+蔬菜";
}
}
这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了。
public class Test {
public static void main(String[] args) {
Food food = new Bread(new Vegetable(new Cream(new Food("香肠"))));
System.out.println(food.make());
}
}
看到没有,一层一层封装,我没从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象,这个设计模式简直跟现实生活中一摸一样,看懂了吗?
我们看看运行结果吧
总结
1. 单例模式:
实现方式:
a) 将被实现的类的构造方法设计成private的。
b) 添加此类引用的静态成员变量,并为其实例化。
c) 在被实现的类中提供公共的CreateInstance函数,返回实例化的此类,就是b中的静态成员变量。
应用场景:
优点:
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
3.提供了对唯一实例的受控访问。
4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
5.允许可变数目的实例。
6.避免对共享资源的多重占用。
缺点:
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
使用注意事项:
1.使用时不能用反射模式创建单例,否则会实例化一个新的对象
2.使用懒单例模式时注意线程安全问题
3.单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
适用场景:
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。
以下都是单例模式的经典使用场景:
1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。
应用场景举例:
1.外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
2. Windows的TaskManager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
10. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
2.工厂模式
3. 代理模式:
一)静态代理
实现方式:
a) 为真实类和代理类提供的公共接口或抽象类。(租房)
b) 真实类,具体实现逻辑,实现或继承a。(房主向外租房)
c) 代理类,实现或继承a,有对b的引用,调用真实类的具体实现。(中介)
d) 客户端,调用代理类实现对真实类的调用。(租客租房)
二)动态代理
实现方式:
a) 公共的接口(必须是接口,因为Proxy类的newproxyinstance方法的第二参数必须是个接口类型的Class)
b) 多个真实类,具体实现的业务逻辑。
c) 代理类,实现InvocationHandler接口,提供Object成员变量,和Set方法,便于客户端切换。
d) 客户端,获得代理类的实例,为object实例赋值,调用Proxy.newproxyinstance方法在程序运行时生成继承公共接口的实例,调用相应方法,此时方法的执行由代理类实现的Invoke方法接管。
jdk动态代理使用的局限性:
通过反射类Proxy和InvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。
4.观察者模式
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
实现方式:
a) 角色抽象类(提供对观察者的添加,删除和通知功能)。
b) 角色具体类,实现a,维护一个c的集合(对角色抽象类的实现)。
c) 观察者抽象类(被角色通知后实现的方法)。
d) 观察者实现类,实现c(多个)。
注:JDK提供了对观察者模式的支持,使用Observable类和Observer接口
两种模型(推模型和拉模型):
■ 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
■ 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
5.适配器模式
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
1. 类适配器(子类继承方式)
实现方式:
a) 目标抽象角色(定义客户要用的接口)
b) 适配器(实现a继承c,作为一个转换器被客户调用)
c) 待适配器(真正需要被调用的)
d) 客户端(借用a的实例调用c的方法)
2. 对象适配器(对象的组合方式)
实现方式:
a) 目标抽象角色(定义客户要用的接口)
b) 适配器(实现a,维护一个c的引用,作为一个转换器被d调用)
c) 待适配器(真正需要被调用的)
d) 客户端(此类,借用a类的实例调用c类的方法,类似静态代理,但是解决的问题不同)
3. 缺省的方式
实现方式:
a) 抽象接口
b) 实现a的适配器类(空实现)
c) 客户端,继承b,调用b中的方法,不必直接实现a(直接实现a需要实现a中的所有的方法)
适配器模式的优点:
1.更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
2.更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
适配器模式的缺点:
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
6.装饰者模式
实现方式:
a) 抽象的被装饰角色 (所有的角色都要直接或间接的实现本角色)
b) 具体的被装饰角色,实现或继承a (被功能扩展的角色)
c) 装饰角色,实现或继承a (本类有对a的引用,所有的具体装饰角色都需要继承这个角色)
d) 多个具体修饰角色 ,继承c(对被装饰角色的功能扩展,可以任意搭配使用)
意图:
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。该模式以对客 户端透明的方式扩展对象的功能。
适用环境:
(1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
(2)处理那些可以撤消的职责。
(3)当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的 子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。