在程序开发中,设计模式的重要性不言而喻。
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
Java的设计模式共有23种,本篇博客将一一为大家介绍每种设计模式的实现原理及作用。
1. 工厂模式
在实际生活中,工厂是生产各种各样产品的地方,而站在程序的角度,工厂的生产是创建的过程。
1.1 简单工厂模式
假设我们有一个生产轿车的工厂,可以生产宝马和奥迪两种轿车,此时,有一个客户来买车,他就有宝马和奥迪两种选择,对于客户而言,他并不需要知道轿车的生产过程,他只需要知道车的品牌和质量是好的。
我们通过代码来实现一下。
首先我们应该提取出每种车的共性,创建一个接口,Car。
package com.itcast.simple;
//抽象(车)
public interface Car {
//启动车辆
public void run();
}
然后分别创建子类BmwCar和AodiCar。
package com.itcast.simple;
public class BmwCar implements Car {
@Override
public void run() {
System.out.println("宝马轿车启动");
}
}
package com.itcast.simple;
public class AodiCar implements Car {
@Override
public void run() {
System.out.println("奥迪轿车启动");
}
}
既然是工厂模式,我们就需要提供一个生产这两种车的工厂类。创建CarFactory。
package com.itcast.simple;
//生产轿车的工厂类
public class CarFactory {
// 生产宝马轿车、奥迪轿车
public static Car productCar(String message) {
if ("宝马".equals(message)) {
return new BmwCar();
} else if ("奥迪".equals(message)) {
return new AodiCar();
} else {
throw new RuntimeException("没有您要的车型");
}
}
}
这样一个简单的工厂模式就完成了,我们来测试一下,编写测试代码。
package com.itcast.simple;
//客户(消费者)
public class Customer_s {
public static void main(String[] args) {
Car bmwCar = CarFactory.productCar("宝马");
bmwCar.run();
Car aodiCar = CarFactory.productCar("奥迪");
aodiCar.run();
Car benChiCar = CarFactory.productCar("奔驰");
benChiCar.run();
}
}
运行测试代码,输出如下:
宝马轿车启动
奥迪轿车启动
Exception in thread "main" java.lang.RuntimeException: 没有您要的车型
at com.itcast.simple.CarFactory.productCar(CarFactory.java:13)
at com.itcast.simple.Customer_s.main(Customer_s.java:13)
既然是简单的工厂模式,那么它肯定是存在弊端的。比如这个时候工厂需要增加一种车型的生产,那我们就得去修改工厂类的代码从而能够提供该车型,当又增加一种车型的时候,我们又得修改工厂类代码,这是不符合面向对象设计原则中的开闭原则的,开闭原则是::对扩展开放,对修改关闭。
1.2 工厂方法模式
刚才的简单工厂模式显然是存在弊端的,扩展性非常差,那么该怎么设计能够让它符合开闭原则呢?这就需要将它设计成工厂方法模式。
在刚才的代码基础上,我们进行适当的修改。
将CarFactory设计为接口,提供生产轿车的抽象方法,从而将生产交给子类完成。
package method;
//抽象工厂
public interface CarFactory {
//创建轿车(抽象)
public Car productCar();
}
然后创建相应的子类工厂。
package method;
public class BmwCarFactory implements CarFactory {
@Override
public Car productCar() {
return new BmwCar();
}
}
package method;
public class AodiCarFactory implements CarFactory {
@Override
public Car productCar() {
return new AodiCar();
}
}
工厂方法模式就编写完成了,我们测试一下。
package method;
public class Customer_m {
public static void main(String[] args) {
CarFactory bmwCarFactory = new BmwCarFactory();
bmwCarFactory.productCar().run();
CarFactory aodiCarFactory = new AodiCarFactory();
aodiCarFactory.productCar().run();
}
}
运行测试代码,输出如下:
宝马轿车启动
奥迪轿车启动
这时候,我们如果想要增加一种生产车型,只需要新建一个类实现CarFactory接口,然后创建该车型的对象并返回即可。
工厂方法模式的优点是更加符合了开闭原则,也符合单一原则,但它也有缺点,就是在增加一个新产品的时候,需要增加一个产品类和一个具体的子工厂,给系统增加负担的同时。每个工厂生产一种产品,太过单一。
1.3 抽象工厂模式
那么由此引出我们的第三个模式,抽象工厂模式。它提供给客户端一个接口,可以创建多个产品族中的产品对象。我们直接通过一段程序来感受一下。
先创建一个接口CarFactory,用于生产车。
package com.itcast.abstr;
//抽象工厂
public interface CarFactory {
// 生产卡车
public Trunk productTrunk();
// 生产轿车
public Sedan productSedan();
}
然后创建宝马和奥迪品牌车,在这之前,我们提取出了两种车的公共部分,写成接口。
package com.itcast.abstr;
//抽象(卡车)
public interface Trunk {
//卡车启动的方法
public void run();
}
package com.itcast.abstr;
//抽象(轿车)
public interface Sedan {
// 轿车启动的方法
public void run();
}
分别创建宝马和奥迪的卡车和轿车。
package com.itcast.abstr;
public class BmwTrunk implements Trunk {
@Override
public void run() {
System.out.println("宝马卡车启动");
}
}
package com.itcast.abstr;
public class BmwSedan implements Sedan {
@Override
public void run() {
System.out.println("宝马轿车启动");
}
}
package com.itcast.abstr;
public class AodiTrunk implements Trunk {
@Override
public void run() {
System.out.println("奥迪卡车启动");
}
}
package com.itcast.abstr;
public class AodiSedan implements Sedan {
@Override
public void run() {
System.out.println("奥迪轿车启动");
}
}
现在,我们来创建两种品牌车的工厂。
package com.itcast.abstr;
//生产宝马品牌的车
public class BmwFactory implements CarFactory {
@Override
public Trunk productTrunk() {
return new BmwTrunk();
}
@Override
public Sedan productSedan() {
return new BmwSedan();
}
}
package com.itcast.abstr;
//生产奥迪品牌的车
public class AodiFactory implements CarFactory {
@Override
public Trunk productTrunk() {
return new AodiTrunk();
}
@Override
public Sedan productSedan() {
return new AodiSedan();
}
}
这样抽象工厂模式就完成了,下面我们编写测试代码。
package com.itcast.abstr;
public class Customer_A {
public static void main(String[] args) {
CarFactory bm = new BmwFactory();
bm.productSedan().run();
bm.productTrunk().run();
CarFactory ad = new AodiFactory();
ad.productSedan().run();
ad.productTrunk().run();
}
}
运行代码,输出如下:
宝马轿车启动
宝马卡车启动
奥迪轿车启动
奥迪卡车启动
会发现,抽象工厂模式和工厂方法模式很相似,只不过抽象工厂模式使得工厂更加灵活,而不像工厂方法模式一样单一。
无论是简单工厂模式、工厂方法模式还是抽象工厂模式,它们都属于工厂模式,在形式和特点上也是极为相似的,它们的最终目的都是为了解耦。使用工厂模式时,只需要关心降低耦合度的目的是否达到了。
2. 单例模式
许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
2.1 饿汉式单例(立即加载方式)
该模式在类加载时就完成了初始化。有两种实现方式。
第一种:
package com.single;
public class Singleton {
private Singleton() {
}
private static Singleton single = new Singleton();
public static Singleton getInstance() {
return single;
}
}
第二种:
package com.single;
public class Singleton {
private Singleton() {
}
private static Singleton single = null;
static {
single = new Singleton();
}
public static Singleton getInstance() {
return single;
}
}
饿汉式单例在类加载初始化时就创建好一个静态对象供外部使用,除非系统重启,这个对象不会改变,所以本身是线程安全的。
2.2 懒汉式单例(延迟加载方式)
该模式在类加载时不会初始化。
package com.single;
//懒汉式单例
public class Singleton {
private Singleton() {
}
private static Singleton single = null;
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
需要注意的是,该实例虽然用延迟加载的方式实现了懒汉式单例,但在多线程环境下会产生多个single对象。
所以,我们对上述代码进行修改。
package com.single;
//懒汉式单例
public class Singleton {
private Singleton() {
}
private static Singleton single = null;
synchronized public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
加上同步锁之后,线程安全问题就迎刃而解了。
虽然问题解决了,但是该方式的运行效率却很低下,下一个线程要想获取对象,就必须等待上一个线程释放锁之后才能继续运行。所以我们应该尽量缩小同步锁的作用范围,继续修改上述代码。
package com.single;
//懒汉式单例
public class Singleton {
private Singleton() {
}
private static Singleton single = null;
public static Singleton getInstance() {
// 双重检索
if (single == null) {
synchronized (Singleton.class) {
if (single == null) {
single = new Singleton();
}
}
}
return single;
}
}
使用双重检索进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。
2.3 静态内容类实现
package com.single;
public class Singleton {
private Singleton() {
}
// 静态内容类
private static class InSideClass {
private static Singleton single = new Singleton();
}
public static Singleton getInstance() {
return InSideClass.single;
}
}
该方式和上述方式其实是一样的,了解一下即可。
3. 建造者模式
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。