今天来说一下Java中的一些常用设计模式:模板设计模式,单例多例模式,工厂设计模式和代理模式。
首先要知道,Java中的设计模式要遵循一个重要的原则:开闭原则(OCP)
开闭原则(OCP): 一个软件实体如类、模块和函数应该对扩展开放、对修改关闭。
一,模板设计模式
模板方法定义了一个算法的步骤,并允许子类为一个或者多个步骤提供具体实现。
举个简单的例子:比如我是个卖包子的,要做不同馅的包子,做土豆馅的包子要经过调土豆馅,揉面,包土豆包子,烧水,蒸包子,包子出笼这几个步骤;同理,我要做肉包子要经过调肉馅,揉面,包肉包子,烧水,蒸包子,包子出笼这些步骤。上述两例中,揉面,烧水,蒸包子,包子出笼这些步骤都是相同的。如果我每做一类的包子,就得把这些步骤的实现统一写一遍,那势必会导致大量的代码冗余。于是为了解决这个问题,我么们定义了一个父类,这个父类主要干两件事:(1)将同样的步骤在父类中实现,通过子类继承下去,子类不需要再写重复这些代码,比如揉面,烧水,蒸包子,包子出笼这些步骤跟我具体蒸什么包子没关系,因此就可以在父类中实现;(2)父类中控制任务的实现步骤(保证蒸包子的顺序不会乱)
那么,剩下的我具体要蒸什么包子调什么馅就可以在具体的子类里面实现。
像上述这种设计方式就叫模板设计模式。
对于以上例子,我们通过代码实现如下:
父类:
abstract class SellSteamBuns {
//模板方法,保证蒸包子的顺序
final void SteamBuns(){ //声明为final以免子类改变这个算法的顺序
MixFilling(); //调馅
KneadDough(); //揉面
MakeBuns(); //包包子
BoilWater(); //烧水
IntoPot(); //入锅
}
//调馅(因为与具体的包子有关,所以定义为抽象方法放到具体子类中实现)
abstract void MixFilling();
//揉面
public void KneadDough(){
System.out.println("揉面");
}
//包包子(因为与具体的包子有关,所以定义为抽象方法放到具体子类中实现)
abstract void MakeBuns();
//烧水
public void BoilWater(){
System.out.println("将水烧开");
}
//入锅
public void IntoPot(){
System.out.println("包子入锅");
}
}
子类1:蒸肉包子
class MeatBun extends SellSteamBuns {
@Override
public void MixFilling(){
System.out.println("调个五花肉馅");
}
public void MakeBuns(){
System.out.println("包个五花肉包");
}
}
子类2:蒸土豆包子
class PotatoBun extends SellSteamBuns {
@Override
public void MixFilling(){
System.out.println("调个土豆馅");
}
public void MakeBuns(){
System.out.println("包个土豆包");
}
}
测试代码:
public class Test {
public static void main(String[] args) {
SellSteamBuns potato=new PotatoBun();
SellSteamBuns meat=new MeatBun();
System.out.println("想吃土豆包...");
potato.SteamBuns();
System.out.println("想吃肉包子...");
meat.SteamBuns();
}
}
二,单例模式
单例模式:简单来说,就是一个类只能构建一个对象的设计模式
要满足一个类只能构建一个对象,那么这个类就不能随便去做new操作,因此这个类的的构造方法必须私有。
特点:构造方法私有化,外部无法产生新的实例化对象,只能通过static方法取得实例化对象
接下来看代码先简单理解单例模式:
public class Singleton {
//私有构造方法保证外部类不能实例化此类对象
private Singleton(){};
//使用static在类的内部产生实例化对象
private static Singleton Instance = null;//单例对象
//get方法返回一个实例Instance
public static Singleton getInstance() {
if(Instance == null){
Instance = new Singleton();
}
return Instance;
}
}
解释几个地方:
(1)产生内部实例化对象时为什么要用static进行修饰?
原因:Singleton内部的instance对象(属性)是一个普通属性,所有的普通属性必须在有实例化对象的时候才能进行内存空间的分配,而由于构造函数的私有性使得外部无法产生实例化对象,所以想要在Singleton没有实例化对象产生的时候,也可以将instance进行使用时,因此就必须使用static关键字对对象进行修饰。
(2)返回实例对象的get方法为什么要用static 进行修饰?
原因:因为要访问的对象是static进行修饰的,所以必须要用static方法进行访问。该方法由类直接调用
单例设计模式的两种形式:懒汉式和饿汉式
懒汉式:在需要的时候再创建类的实例化对象
代码如下:
public class Singleton {
private Singleton(){};
private static Singleton Instance = null;//单例对象
public static Singleton getInstance() {
//需要的时候才创建
if(Instance == null){
Instance = new Singleton();
}
return Instance;
}
}
饿汉式:直接创建出类的实例化对象
代码如下:
public class Singleton {
private Singleton(){};
//直接创建,final保证整体创建中只能有一个实例对象
private final static Singleton Instance = new Singleton();//单例对象
public static Singleton getInstance() {
return Instance;
}
}
懒汉饿汉的比较:
(1)懒汉式存在多线程安全问题,而饿汉式不存在。
(2)懒汉是以时间换空间,饿汉是以空间换时间
如何解决懒汉式的线程安全问题?
答:加锁,采用双重检测机制
public class Singleton {
private Singleton(){}
private volatile static Singleton Instance = null;//单例对象
public static Singleton getInstance() {
//需要的时候才创建
if(Instance == null){ // 双重检测机制
synchronized(Singleton.class){ //同步锁
if(Instance==null){ // 双重检测机制
Instance = new Singleton();
}
}
}
return Instance;
}
}
注:为了防止指令重排导致的线程不安全问题引入了volatile关键字来保证JVM按顺序执行指令,从而保证线程安全。
三,多例模式
多例模式:一个类可以有多个实例(所谓多例只是比单例追加了更多个内部实例化对象产生而已)。
特点(同单例一样):
- 构造方法私有化。
- 类内部一定会提供一个static方法用于取得实例化对象。
代码如下:
public class Multiton {
private Multiton(){}
private static Multiton Male=new Multiton();
private static Multiton Female=new Multiton();
public static Multiton getInstance(int flag){
switch(flag){
case 1:
return Male;
case 2:
return Female;
default:
return null;
}
}
}
其实多例比单例就是在类内部多创建了几个对象而已
四,代理模式
代理模式:两个子类共同实现一个接口,其中一个子类负责真实业务实现,另外一个子类完成辅助真实业务主体的操作
具体框架如下:
接口类:
public interface ISubject {
public void buyComputer(); //核心功能是买电脑
}
负责真实业务实现的子类:
public class RealSubject implements ISubject {
public void buyComputer(){
System.out.println("买一台戴尔电脑");
}
}
辅助真实业务的代理类:
/**
* 代理类
* */
public class ProxySubject implements ISubject {
private ISubject subject; //真正的操作业务
public ProxySubject(ISubject subject) {
this.subject = subject;
}
public void FirstProduct(){
System.out.println("1,先生产一个电脑");
}
public void AfterServer(){
System.out.println("3,电脑售后服务");
}
public void buyComputer(){
this.FirstProduct(); //真实操作前准备
this.subject.buyComputer();//调用真实业务
this.AfterServer(); //操作后的收尾
}
}
工厂类:用于生产电脑
public class Factory {
public static ISubject getInstance(){
return new ProxySubject(new RealSubject());
}
}
测试代码
public class Test {
public static void main(String[] args) {
ISubject subject= Factory.getInstance();
subject.buyComputer();
}
}
代理模式的本质:所有的真实业务操作都会有一个与之辅助的工具类(功能类)共同完成。
五,工厂设计模式
简单工厂模式
简单工厂模式:专门定义一个类用来创建其它类的实例,被创建的实例通常都具有共同的父类。
这里我们相当于是创建生产电脑的工厂,客户需要购买什么样的电脑,只要输入类型编号就可以获取该电脑。将类的实例化交给工厂易于解耦。
// 抽象产品类的接口:
public interface Computer {
void printComputer();
}
// 生产电脑的工厂:
public class computerFactory {
public static Computer getInstance(String type){
Computer computer=null;
if(type.equals("macbook")){
computer=new MacbookComputer();
}
if(type.equals("surface")){
computer=new SurfacebookComputer();
}
return computer;
}
}
// 具体产品类1
public class MacbookComputer implements Computer{
@Override
public void printComputer() {
System.out.println("this is a MacbookComputer");
}
}
//具体产品类2
public class SurfacebookComputer implements Computer {
@Override
public void printComputer(){
System.out.println("this is a surfaceComputer");
}
}
// 客户端
public class Client {
public void BuyComputer(Computer computer){
computer.printComputer();
}
public static void main(String[] args) {
Client client=new Client();
System.out.println("请输入你想要的电脑型号:");
Scanner sc=new Scanner(System.in);
String type=sc.next();
//将客户端的需求交给工厂类来实例化出客户需要的电脑
Computer computer=computerFactory.getInstance(type);
//客户买电脑
client.BuyComputer(computer);
}
}
优点:
(1)简单易于实现
(2)把类的实例化交给工厂,易于解耦
缺点:添加具体产品需要修改工厂违反OCP开放封闭原则
工厂方法模式
工厂方法模式:定义一个用来创建对象的接口,让子类决定实例化哪一个类,让子类决定实例化延迟到子类。
工厂方法模式是针对每个产品提供一个工厂类,在客户端中判断使用哪个工厂类去创建对象。
将之前的 ComputerFactory 抽象成一个接口,那么创建相应具体的工厂类去实现该接口的方法。
代码如下:
// 抽象产品类的接口
public interface Computer {
void printComputer();
}
//抽象工厂类的接口
public interface computerFactory {
Computer createComputer();
}
// 具体产品类1
public class MacbookComputer implements Computer {
@Override
public void printComputer() {
System.out.println("this is a MacbookComputer");
}
}
// 具体产品类2
public class SurfacebookComputer implements Computer{
@Override
public void printComputer(){
System.out.println("this is a surfaceComputer");
}
}
// 具体工厂类1(生产第1类产品)
public class AppFactory implements computerFactory {
public Computer createComputer(){
return new MacbookComputer();
}
}
//具体工厂类2(生产第2类产品)
public class AndrFactory implements computerFactory {
public Computer createComputer(){
return new SurfacebookComputer();
}
}
// 客户端
public class Client {
// 客户端买电脑任务
public void BuyComputer(Computer computer){
computer.printComputer();
}
public static void main(String[] args) {
Client client=new Client();
computerFactory factory=new AppFactory(); //工厂生产电脑
client.BuyComputer(factory.createComputer()); //客户买工厂所生产的电脑
}
}
简单工厂模式 和 工厂方法模式的比较:
(1) 对于简单工厂模式而言,创建对象的逻辑判断放在了工厂类中,客户不感知具体的类,但是其违背了开闭原则,如果要增加新的具体类,就必须修改工厂类。
(2) 对于工厂方法模式而言,是通过扩展来新增具体类的,符合开闭原则,但是在客户端就必须要感知到具体的工厂类,也就是将判断逻辑由简单工厂的工厂类挪到客户端。
(3) 工厂方法横向扩展很方便,假如该工厂又有新的产品要生产,那么只需要创建相应的工厂类和产品类去实现抽象工厂接口和抽象产品接口即可,而不用去修改原有已经存在的代码。
优点:
降低了代码耦合度,对象的生成交给子类去完成
实现了开放封闭原则 - 每次添加子产品 不需要修改原有代码
缺点:
增加了代码量,每个具体产品都需要一个具体工厂
当增加抽象产品 也就是添加一个其他产品族 需要修改工厂 违背OCP
抽象工厂模式
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
工厂方法模式和抽象工厂模式基本类似,可以这么理解:当工厂只生产一个产品的时候,即为工厂方法模式,而工
厂如果生产两个或以上的商品即变为抽象工厂模式。
我们在抽象工厂接口中新增创建系统的方法,并由实例工厂类去实现。
// 抽象产品类的接口
interface Computer {
void printComputer();
}
// 具体产品类1
class MacbookProComputer implements Computer {
public void printComputer() {
System.out.println("This is a MacbookPro");
}
}
// 具体产品类2
class SurfaceBookComputer implements Computer {
public void printComputer() {
System.out.println("This is a SurfaceBook");
}
}
//操作系统的接口
interface OperatingSystem {
void printSystem();
}
// 具体操作系统类1
class MacOsSystem implements OperatingSystem {
public void printSystem() {
System.out.println("This is a mac os");
}
}
// 具体操作系统类2
class Windows8System implements OperatingSystem {
public void printSystem() {
System.out.println("This is a window 8");
}
}
// 抽象工厂类接口
interface ProductionFactory {
Computer createComputer();
OperatingSystem createSystem();
}
// 具体工厂类1
class AppleFactory implements ProductionFactory {
//生产第1类产品
public Computer createComputer() {
return new MacbookProComputer();
}
//生产第1类产品对应的操作系统1
public OperatingSystem createSystem() {
return new MacOsSystem();
}
}
// 具体工厂类2
class MsFactory implements ProductionFactory {
//生产第2类产品
public Computer createComputer() {
return new SurfaceBookComputer();
}
//生产第2类产品对应的操作系统2
public OperatingSystem createSystem() {
return new Windows8System();
}
}
//客户端
public class Client {
//客户买电脑
public void buyComputer(Computer computer) {
computer.printComputer();
}
//客户用操作系统
public void use(OperatingSystem s) {
s.printSystem();
}
public static void main(String[] args) {
Client client = new Client();
ProductionFactory factory = new AppleFactory();
Computer computer = factory.createComputer();
OperatingSystem system = factory.createSystem();
client.buyComputer(computer);
client.use(system);
}
}
优点:
(1) 代码解耦
(2) 实现多个产品族(相关联产品组成的家族),而工厂方法模式的单个产品,可以满足更多的生产需求
(3) 很好的满足OCP开放封闭原则
(4) 抽象工厂模式中我们可以定义实现不止一个接口,一个工厂也可以生成不止一个产品类 对于复杂对象的生产相当灵活,易扩展
缺点:
(1) 扩展产品族相当麻烦 而且扩展产品族会违反OCP,因为要修改所有的工厂
(2) 由于抽象工厂模式是工厂方法模式的扩展 总体的来说 很笨重
总结:
简单工厂模式最大的优点就是工厂内有具体的逻辑去判断生成什么产品,将类的实例化交给了工厂,这样当我们需要什么产品只需要修改工厂的调用而不需要去修改客户端,对于客户端来说降低了与具体产品的依赖
工厂方法模式是简单工厂的扩展,工厂方法模式把原先简单工厂中的实现那个类的逻辑判断交给了客户端,如果像添加功能只需要修改客户和添加具体的功能,不用去修改之前的类。
抽象工厂模式进一步扩展了工厂方法模式,它把原先的工厂方法模式中只能有一个抽象产品不能添加产品族的缺点克服了,抽象工厂模式不仅仅遵循了OCP原则,而且可以添加更多产品(抽象产品),具体工厂也不仅仅可以生成单一产品,而是生成一组产品,抽象工厂也是声明一组产品,对应扩展更加灵活,但是要是扩展族系就会很笨重。