Java最全的设计模式之创建型模式

Java设计模式之创建型模式

1.设计模式的概述

1.1什么是模式?

​ 模式就是在特定环境下人们解决某类重复出现问题的一套成功或者有效的解决方案。

1.2设计模式的发展

设计模式是从软件模式中一步步总结而来,最开始的23种设计模式,也是最经典的23种设计模式。

1.3设计模式的定义?

​ 设计模式是在特定环境下为解决某一通用软件设计问题提供的一套定制的解决方案,该方案描述了对象和类之间的相互作用。

​ 设计模式就是一套被反复使用的,多数人知晓的,经过分类编目的,代码设计经验的总结,使用设计模式是为了可重用代码,让代码更容易被他人理解并且提高代码的可靠性。

1.4设计模式的基本要素
  • 模式名称
  • 问题
  • 解决方案
  • 效果
1.5 如何研究一个设计模式
  • 模式概述
  • 模式的结构与实现
  • 模式应用实例
  • 模式扩展部分
  • 模式的优缺点与适应的环境
1.6设计模式的分类
1.6.1 根据目的分类
  • 创建型 : 工厂方法模式, 抽象工厂模式 ,建造者模式,原型模式,单例模式
  • 结构型 :适配器模式 ,组合模式,装饰模式,外观模式,桥接模式,享元模式,代理模式
  • 行为型 :职责链模式,命令模式,解释器模式,迭代器模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,模板方法模式,访问者模式
1.6.2根据范围分类
  • 可分为类模式
  • 对象模式两种。
1.7设计模式的优点
  1. ​ 设计模式融合了众多专家的经验,并且以一种标准的形式提供给广大开发者使用
  2. 设计模式使人们可以更加简单,方便的复用成功的设计和体系结构。
  3. 设计模式使设计方案更加灵活,并且易于修改。
  4. 设计模式的使用提高了软件系统的开发效率和质量,并且一定程度上节约了设计成本
  5. 设计模式更有助于理解面向对象思想。

2.面向对象的设计原则

2.1单一职责原则

一个对象应该只包含单一的职责,并且被完整的封装在一个类中

2.2开闭原则

软件实体应当对扩展开放,对修改关闭

2.3里氏代换原则

所有引用基类的地方必须能透明的使用其子类的对象

2.4依赖倒转原则

高层模块不应该依赖底层模块,他们都应该依赖抽象。抽象不应该依赖细节,细节应该依赖于抽象

2.5接口隔离原则

客户端不应该依赖那些不需要的接口

2.6合成复用原则

优先使用对象组合,而不是通过继承来达到复用的目的

2.7迪米特法则

每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位

3.简单工厂模式

3.1简单工厂模式的定义

简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数不同返回不同的实例,被创建的实例通常都具有共同的父类。
举个很简单的例子,就是水果工厂中有各种水果,农场可以通过用户需要的水果,只需要提供水果的名称就可以得到水果。水果工厂就相当于工厂类,而每个水果就是产品的实体类。

3.2简单工厂模式结构
  • Factory(工厂角色):它时简单工厂模式的核心,负责创建产品实例的内部逻辑,提供静态工厂方法,返回抽象产品类Product
  • Product(抽象产品角色):它时工厂创建的父类
  • ConcreteProduct(具体产品角色):它就是工厂创建的目标,每个具体产品角色都继承了抽象产品角色
3.3简单工厂模式的实现

如果我们要生产水果,就需要一个抽象的水果类,和几个水果的实例类,然后工厂类中去提供对水果的实例化。
第一步先写抽象产品类

public abstract class Product {
    //定义抽象公共方法
	public void say(){
		System.out.println("我是水果");
	}
	//子类都要实现的特有方法
	public abstract  void kide();
}

第二步写产品实现类

public class ConcreteProduct extends Product {
	@Override
	public void kide() {
		System.out.println("我是苹果");
	}
}
//上边是英语,下边是日语
public class ConcreteProduct2 extends Product {
	@Override
	public void kide() {
		System.out.println("我是香蕉");
	}
}

第三步,写工厂类提供实现方式

public class Factory {
	public static Product getProduct(String language){
		Product  product=null ;
        //返回product实体对象,根据不同的参数
		if(language.equals("apple")){
			return product =new ConcreteProduct();
		}else if(language.equals("banana")){
			return product = new ConcreteProduct2();
		}
		return product;
	}
}

第四步测试

public class Test {
	public static void main(String[] args) {
		Product english = Factory.getProduct("apple");
		english.kide();
		english.say();
		Product japanese = Factory.getProduct("banana");
		japanese.say();
		japanese.kide();
	}
}

在实际应用过程中,更多是面向接口编程,因为Java支持单继承多实现,所以尽量将方法都定义在接口中,更加容易复用,也可以实现对接口功能的细分,让每个接口实现的公共更加细致。

如果有子类比较常用的公共方法也可写一个抽象类,让子类都去继承这个抽象类,也可让代码简化。

3.4 简单工厂模式的优缺点
  • 优点: 使用者无须知道实例类名,只需记住对应的参数,就可以拿到相应的实例对象。
  • 缺点:工厂拓展性不好,增加子类需要修改源码,工厂方法为静态,工厂角色无法形成基于继承的等级结构
3.5简单工厂模式适用的环境

工厂类负责对象较少,客户端只知道传入参数,对创建对象毫不关心。

4.工厂方法模式

4.1 工厂方法模式的定义

​ 定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
举个例子,就是如果要生成一个按钮,按钮也有不同的种类,每一种就相当于一个按钮工厂,所以每一按钮工厂也应该有同一个抽象按钮工厂,每个按钮工厂也可以生产一类抽象类按钮,我们通过实现不同的按钮的抽象类,就可以得到不同颜色的按钮。

4.2工厂方法模式的结构
  • Product(抽象产品):它定义产品的接口,是工厂方法模式所创建对象的超类,是产品对象公共的父类
  • ConcreteProduct(具体产品):它实现抽象产品的接口,专门具体工厂的创建
  • Factory(抽象工厂):在抽象工厂类中声明了抽象工厂方法,用于返回一个产品,所有工厂都应该实现接口
  • ConcreteFactory(具体工厂):它是抽象工厂的子类,实现抽象工厂中的抽象方法,返回一个具体产品类的实例。
4.3工厂方法模式的实现

如果我们要写一个日志记录器,用户可以通过改变配置类,来修改日志的记录方式
第一步先写日志类接口

public interface Logger {
	public void writeLog();
}

第二步 再分别实现每个日志

public class FileLogger implements Logger {
	@Override
	public void writeLog() {
		System.out.println("文件日志记录");
	}
}
//上边是文件日志,下边是数据库日志
public class DatabaseLogger implements  Logger {
	@Override
	public void writeLog() {
		System.out.println("数据库日志记录");
	}
}

第三步写实现工厂的接口

public interface Factory {
	public Logger createLogger();
}

第四步分别写日志的实现工厂类

public class DatabaseFactory implements Factory {
	
	@Override
	public Logger createLogger() {
		//连接数据库
		//创建数据库日志处理对象
		Logger logger =new DatabaseLogger();
		
		return logger;
	}
}
public class FileFactory implements Factory {
	@Override
	public Logger createLogger() {
		//创建文件记录日志对象
		Logger logger =new FileLogger();
		
		return logger;
	}
}

第五步测试

	public static void main(String[] args) {
		Factory factory =new DatabaseFactory();
		Logger logger = factory.createLogger();
		logger.writeLog();

		Factory factory1 =new FileFactory();
		Logger logger1 = factory1.createLogger();
		logger1.writeLog();
    }

其实这种对于开发者角度来看,很实用因为比起普通工厂模式来说,要有了很强的拓展性,但是我们应该将实例化,用更简单的方式展现在面前跟源码分离,下面有一种基于Xml方式的实例方法

创建一个xml

<?xml version="1.0"?>
<congig>
    <!--把类的全路径写入-->
    <className>com.itcast.MethodFactory.DatabaseFactory</className>
    <className>com.itcast.MethodFactory.FileFactory</className>
</congig>

我们在创建一个工具类

public class XMLUtil {
	public static  Object getBean(int index){	
		try {
            //先获取dom对象
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(new File("src//com//itcast//MethodFactory//config.xml"));
			//通过dom对象拿到对应的节点,节点的值就是类名
			NodeList className = document.getElementsByTagName("className");
			Node node = className.item(index).getFirstChild();
			String name = node.getNodeValue();
			//通过反射得到类的对象
			Class c = Class.forName(name);
			Object o = c.newInstance();
			return o;					
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}	
	}
}

然后再进行测试

	public static void main(String[] args) {
        //我们只需要填入类名在xml中对应的顺序的下标,就可以拿到相应的实例对象工厂
		Factory bean = (Factory) XMLUtil.getBean(0);
		Logger logger = bean.createLogger();
		logger.writeLog();		
	}
}
4.4工厂方法的改进
  • 为了让代码有更多的使用性,我们可以采用方法重载,比如数据库日志中可以传入数据库参数,在工厂中进行数据库的连接,也可以传入一个封装参数的对象,来初始化数据库日志。
  • 为了进一步简化客户端的操作,我们可以吧日志方法的实现放在对应的工厂中实现,把工厂转化为抽象类,就可以保护代码,是代码隐藏起来。我们只需要调用工具类得到工厂对象,然后调用工厂方法,就可以实现日志方法。
4.5工厂方法模式优缺点
  • 优点 : 在工厂方法模式中,向用户隐藏了产品类的创建细节,用户只需要关心,相应的产品对应的工厂。基于工厂角色和产品角色的多态性设计是实现工厂方法模式的关键。使用工厂模式,当加入新产品时,无须修改抽象工厂,和抽象产品提供的接口,也无须修改其他产品
  • 缺点: 添加商品时,产品和对应的工厂是成对出现的,增加了系统的复杂度。
4.6工厂方法的适用环境

客户端不知道所需要的对象类,只需要关心对象对应的工厂,抽象工厂只需要提供一个创建产品的接口,抽象方法通过子类来创建对象。

5.抽象工厂模式

5.1抽象工厂模式定义

​ 提供一个创建一系列相关或者相互依赖对象的接口,而无须指定他们的具体类。
举个例子在成产家用电器的厂商,要生成冰箱,电视机,空调,但是一般不止一个品牌,一般都是好几种牌子,所以,抽象的工厂就定义了成产不同的产品的抽象方法,每个工厂就就实现了每个抽象方法就可以生产不同的电器。

5.2抽象工厂的结构
  • AbstractFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每个方法对应一种产品
  • ConcreteFactory(具体工厂):它实现了抽象工厂中声明创建产品的方法,生成一组具体产品
  • AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明所有产品的业务方法
  • ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法
5.3抽象工厂的实现

我们引入一个例子,假如有一个皮肤库,有两种风格,一种Spring绿色的,一种是Summer蓝色的,他们同样的渲染了Button(按钮) ,文本框,组合框

首先我们要写组件的接口

public interface Button {
	public void display();
}
public interface ComboBox {
	public void display();
}
public interface TextField {
	public void display();
}

然后再写每个的实现类,根据不同需求实现不同

public class SpringButton implements Button {
	@Override
	public void display() {
		System.out.println("显示绿色的按钮");
	}
}
public class SpringComboBox implements ComboBox {
	@Override
	public void display() {
		System.out.println("显示绿色的组合框");
	}
}
public class SpringTextField implements  TextField {
	@Override
	public void display() {
		System.out.println("显示绿色的文本框");
	}
}
public class SummerButton implements Button {
	@Override
	public void display() {
		System.out.println("显示浅蓝色的按钮");
	}
}
public class SummerComboBox implements ComboBox {
	@Override
	public void display() {
		System.out.println("显示浅蓝色的组合框");
	}
}
public class SummerTextField implements  TextField {
	@Override
	public void display() {
		System.out.println("显示浅蓝色的文本框");
	}
}

再写工厂类

public interface SkinFactory {
	public Button createButton();
	public TextField createTextField();
	public ComboBox createComboBox();
}
public class SpringSkinFactory implements SkinFactory{
	
	@Override
	public Button createButton() {
		return new SpringButton();
	}
	
	@Override
	public TextField createTextField() {
		return new SpringTextField();
	}
	
	@Override
	public ComboBox createComboBox() {
		return new SpringComboBox();
	}
}


public class SummerSkinFactory implements SkinFactory {
	@Override
	public Button createButton() {
		return new SummerButton();
	}
	
	@Override
	public TextField createTextField() {
		return new SummerTextField();
	}
	
	@Override
	public ComboBox createComboBox() {
		return new SummerComboBox();
	}
}

和之前一样引入xml工具类,去做配置文件,将工厂类名写入xml配置文件中,

<?xml version="1.0" ?>
<config>
    <className>com.itcast.AbstractFactory.Test1.factory.SpringSkinFactory</className>
    <className>com.itcast.AbstractFactory.Test1.factory.SummerSkinFactory</className>

</config>
public class XMLUtil {
	public static  Object getBean(int index){
		
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(new File("src//com//itcast//AbstractFactory//config.xml"));
			
			NodeList className = document.getElementsByTagName("className");
			Node node = className.item(index).getFirstChild();
			String name = node.getNodeValue();
			
			Class c = Class.forName(name);
			Object o = c.newInstance();
			return o;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}

测试

public class AbstractTest {
	public static void main(String[] args) {
		SpringSkinFactory spring = (SpringSkinFactory) XMLUtil.getBean(0);
		spring.createButton().display();
		spring.createTextField().display();
		spring.createComboBox().display();
		SummerSkinFactory summer =(SummerSkinFactory)XMLUtil.getBean(1);
		summer.createButton().display();
		summer.createTextField().display();
		summer.createComboBox().display();
	}
}

5.4 抽象工厂的优缺点
  • 优点:抽象类隔离了具体类的形成,使得创建一个一类产品更加方便,添加这类产品时,只需要实现相应的细节接口,以及对于的工厂类。
  • 缺点:无法增加组件,只能生产一类产品。
5.5抽象工厂的适用环境

适用于只创建一类型产品族,产品等级结构稳定,在设计完成之后不会向其中增加新的产品等级结构或者删除原有的产品等级结构。

6.建造者模式

6.1建造者模式定义

将一个复杂对象对的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
举个例子,在生产一个汽车的时候,汽车的需要不同的零件组成,每个零件的生产都不同,用户也不知道如何装配,就需要一个构建者把这些组成一个完整的汽车返回给用户

6.2建造者模式结构
  • Builder(抽象建造者):它为创建一个产品对象的各个部位指定抽象接口,里面声明不同的建造抽象方法
  • ConcreteBuilder(具体建造者):实现Builder接口,实现每个部件的构建方法
  • Product(产品):它时被构建的复杂对象,包含多个零件
  • Director(指挥者):负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在方法中调用建造者对象的部件与装配方法,完成对象的建造。
6.3建造者模式实现

我们引入一个实例,游戏公司要创建角色,每个角色所具有的部分都是一样

所以首先写一个实体类对象

public class Actor {
	private String type;
	private String sex;
	private String face;
	private String costume;
	private String hairStyle;
	//getter  setter    toString   方法省略

然后我们需要一个建造者的抽象

public abstract class ActorBuilder {
	//把actor定义为protected,就可以实现类的子类访问到这个对象,以免重复创建
	protected Actor actor =new Actor();
    
	public abstract void buildType();
	public abstract void buildSex();
	public abstract void buildFace();
	public abstract void buildCostume();
	public abstract void buildHairStyle();
	
	public Actor createActor(){
		return actor;
	}
	
}

每一个不同的角色都实现这个建造者,后边只要调用相应的建造者,就会得到对应的角色。

public class AngleActor extends  ActorBuilder{
	
	@Override
	public void buildType() {
		actor.setType("天使");
	}
	
	@Override
	public void buildSex() {
		actor.setSex("女");
	}
	
	@Override
	public void buildFace() {
		actor.setFace("漂亮");
	}
	
	@Override
	public void buildCostume() {
		actor.setCostume("百褶裙,黑丝");
	}
	
	@Override
	public void buildHairStyle() {
		actor.setHairStyle("披肩长发");
	}
}

public class HeroActor extends  ActorBuilder{
	
	@Override
	public void buildType() {
		actor.setType("英雄");
	}
	
	@Override
	public void buildSex() {
		actor.setSex("男");
	}
	
	@Override
	public void buildFace() {
		actor.setFace("英俊");
	}
	
	@Override
	public void buildCostume() {
		actor.setCostume("盔甲");
	}
	
	@Override
	public void buildHairStyle() {
		actor.setHairStyle("飘逸");
	}
}
public class DevilActor extends  ActorBuilder{
   
   @Override
   public void buildType() {
      actor.setType("恶魔");
   }
   
   @Override
   public void buildSex() {
      actor.setSex("妖");
   }
   
   @Override
   public void buildFace() {
      actor.setFace("丑陋");
   }
   
   @Override
   public void buildCostume() {
      actor.setCostume("黑衣");
   }
   
   @Override
   public void buildHairStyle() {
      actor.setHairStyle("光头");
   }
}

写个一个指挥者,之前实现的建造者,里面有很多方法,去构件角色的每个部分,如果比较复杂,我们就需要一个控制者,去指挥这一切

public class ActorController {
	public Actor construct(ActorBuilder ab){
		Actor actor;
		ab.buildType();
		ab.buildSex();
		ab.buildCostume();
		ab.buildFace();
		ab.buildHairStyle();
        //这里要主题次序,一定要在建造完事后,再调用creatActor()方法
		actor = ab.createActor();
		return actor;
	}
}

测试,这个同样的用了之前的xml的工具类,不要忘记配置xml配置文件

public class BuildTest {
	public static void main(String[] args) {
		ActorBuilder hero =(ActorBuilder) XMLUtil.getBean(0);
		
		ActorController controller =new ActorController();
		Actor heroActor = controller.construct(hero);
		
		System.out.println(heroActor.toString());
		
	}
}
6.4对于建造者模式的优化
6.4.1优化一

其实我们完全可以不用指挥者,在建造者的抽象类中就可以直接调用build的方法,完成构件

	public Actor createActor(){
    	this.buildType();
		this.buildSex();
		this.buildCostume();
		this.buildFace();
		this.buildHairStyle();
		return actor;
	}

但是这样对建造者的职责就太大了,单独封装更符合单一职责

6.4.2钩子方法引入

每个角色的创建不应该都一模一样

我们在建造者抽象方法中引入一个钩子方法,isBareheaded()是否光头

public Abstract class ActorBuild{
    ....
	public boolean isBareheaded(){
    	return false;
	}
}

在实现相应的的角色建造者时,如果角色是光头就重写这个方法,并且返回true

public class DevilBuilder extends ActorBuild{
    
    ....
    public boolean isBareheaded(){
        return true;
    }    
}

当然这样指挥者的作用就显现出来了

public class ActorController {
	public Actor construct(ActorBuilder ab){
		Actor actor;
		ab.buildType();
		ab.buildSex();
		ab.buildCostume();
		ab.buildFace();
        //利用钩子函数控制构造
        if(isBareheaded()){
            ab.buildHairStyle();
        }	
        //这里要主题次序,一定要在建造完事后,再调用creatActor()方法
		actor = ab.createActor();
		return actor;
	}
}
6.5建造者模式的优缺点
  • 优点: 使客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,可以创建不同的产品,每一个建造者相互独立,与其他建造者无关。开业更加精细的控制产品创建过程,让创建的过程更加清晰。
  • 缺点:建造者模式建造的产品十分相似,如果产品每个之间的差异性很大,就不适合建造者模式,如果产品,过于复杂,需要定义更多的建造者来实现这种变化,这就使得系统很庞大。
6.6建造者模式适用环境

产品对象有着很复杂的内部结构,产品对象之间属性相互依赖,需要指定先后顺序对象的创建过程独立于创建改对象的类,隔离复杂对象的创建和使用,使其变成相同的创建过程而创建不同的产品。

7.原型模式

7.1原型模式的定义

使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象
就相当于克隆一样,可以很快速的获得一样相同的对象

7.2原型模式的结构
  • Prototype(抽象原型类):声明了克隆方法的接口
  • ConcretePrototype(具体原型类):实现了抽象原型中的克隆方法
  • Client(客户端):让一个原型对象克隆自身从而创建一个新的对象
7.3浅克隆与深克隆
  • 浅克隆:在浅克隆中,复制对象只复制它本身和其中包含值的成员变量(基本类型),而引用类型的成员变量并没有复制。
  • 深克隆:在克隆中,无论原型对象的成员变量是值类型还是引用类型,都会一一复制。
7.4原型模式的实现

先实现浅克隆,先写原型对象,克隆方法使用父类Object中的clone()方法,但是有一个点,一定要实现Cloneable接口,不然会报错。

public class Attachment {
	private String name;
    //gettter  and setter
}
public class WeekLog implements Cloneable{
	private Attachment attachment;
	private String name;
	private String date;
	private String content;
    //getter and settter
    public WeekLog clone(){
		try {
			Object clone = super.clone();
			return  (WeekLog)clone;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
    }
}

测试

public class Client {
   public static void main(String[] args) {
      Attachment attachment =new Attachment();
      WeekLog old = new WeekLog();
      old.setAttachment(attachment);
      
      WeekLog clone = old.clone();
      
      System.out.println(old==clone);//false
      System.out.println(old.equals(clone));//false
      System.out.println(old.getAttachment()==clone.getAttachment());//true
      System.out.println(old.getAttachment().equals(clone.getAttachment()));//true
   }
}

有结果可以知道浅克隆是不能将引用对象克隆,只是把地址值传过去,还是同一个对象

为了实现深克隆,我们引入序列化(Serialization),让每个实体类实现Serialization接口,序列化就是将对象写入到流的过程,原对象依旧在内存中。

public class Attachment implements Serializable {
	private String name;
    //getter and setter
}
public class WeekLog implements Serializable {
	private Attachment attachment;
	private String name;
	private String date;
	private String content;
    //getter and setter
    public WeekLog deepClone() throws IOException, ClassNotFoundException {
		//把对象写入流中
		ByteArrayOutputStream bao = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bao);
		oos.writeObject(this);
		//从流中读取对象
		ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
		ObjectInputStream ois = new ObjectInputStream(bis);
		return (WeekLog) ois.readObject();
	}
}

测试

public class Client {
	public static void main(String[] args) {
		Attachment attachment =new Attachment();
		WeekLog old = new WeekLog();
		old.setAttachment(attachment);
		WeekLog clone = null;
		try {
			 clone = old.deepClone();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	
		System.out.println(old==clone);//false
		System.out.println(old.equals(clone));//false
		System.out.println(old.getAttachment()==clone.getAttachment());//false
		System.out.println(old.getAttachment().equals(clone.getAttachment()));//false
	}
}
7.4原型模式的优缺点
  • 优点:当待创建对象实例比较复杂时,可以简化对线创建的过程,通过复制一个已有的对象,来提高新实例的创建效率,具有很好的扩展性
  • 缺点:要给每个实体类配备克隆方法,对已有类改造比较麻烦,需要修改源码。
7.5原型模式的适用环境

创建新对象的成本较大,新对象可以复制已有对象来获得。系统可以保持对象的状态,而对象状态变化很小。

8.单例模式

8.1单例模式的定义

确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
举个例子,一个电脑里面可以有很多的软件同时运行,但是任务管理器永远只有一个。

8.2单例模式的结构

Singleton(单例):在单例类的内部创建它的唯一实例,通过静态方法getInstance()让客户端保证它的唯一实例。

8.3单例模式的实现

实现单例模式最终要的就是把构造器私有,并且将对象作为静态成员变量,别人就不能new出新 的对象,得到对象的途径只有一个就是getInstance(),只要在方法中进行判断,如果实例对象已经创建就直接返回即可,保证对象的唯一性。(为了保证只有一个实例,1.必须将构造器私有;2.想要直接通过getInstance()获取实例必须将方法静态修饰;3.方法静态之后,方法中的成员变量也一定要静态)

public class LoadBalancer {
	private static LoadBalancer instance= null;
	private List serverList = null;
	
	private LoadBalancer(){
		serverList =new ArrayList();
	}
	public static LoadBalancer getInstance(){
		if(instance==null){
			instance = new LoadBalancer();
		}
		return instance;
	}
	
	public void addServer(String server){
		serverList.add(server);
	}
	public void removeServer(String server){
		serverList.remove(server);
	}
	public String getServer(){
		Random random  =new Random();
		int i = random.nextInt(serverList.size());
		return (String) serverList.get(i);
	}
}

我们模拟负载均衡器 测试代码如下

public class TestServer {
	public static void main(String[] args) {
		LoadBalancer instance1 = LoadBalancer.getInstance();
		LoadBalancer instance2 = LoadBalancer.getInstance();
		LoadBalancer instance3 = LoadBalancer.getInstance();
		LoadBalancer instance4 = LoadBalancer.getInstance();
		if(instance1==instance2 && instance2==instance3 && instance3==instance4){
			System.out.println("所有负载都是均衡的");
		}//所以实例的对象为同一个
		instance1.addServer("server1");
		instance2.addServer("server2");
		instance3.addServer("server3");
		instance4.addServer("server4");
		
		for (int i = 0; i <100 ; i++) {
			String server = instance2.getServer();
			System.out.println(server);
		}//服务器随机出现
	}
}
8.4饿汉式单例和懒汉式单例
public class EagerSingleton {
	private static final EagerSingleton instance= new EagerSingleton();
	
	private EagerSingleton() {
	
	}
	
	public static EagerSingleton getInstance(){
		return instance;
	}
}
public class LazySingleton {
   private static LazySingleton instance= null;
   
   private LazySingleton(){}
   //为了防止并发,所以给实例方法加锁
   synchronized public static LazySingleton getInstance(){
      if(instance==null){
         instance=new LazySingleton();
      }
      return instance;
   }
}

懒汉模式这种加锁的方式很影响效率,我们可以把实例方法赋值的代码块加锁,

public static LazySingleton getInstance(){
      if(instance==null){
          synchronized (LazySingleton.class){
         		instance=new LazySingleton();
          }
      }
      return instance;
   }

但是这种提高了效率但是,也会有并发的缺陷,当两个线程都通过==null的判断时,就会造成创建多个对象,我们可以在锁里面再添加一层判断

public static LazySingleton getInstance(){
      if(instance==null){
          synchronized (LazySingleton.class){
              if(instance==null)
         		instance=new LazySingleton();
          }
      }
      return instance;
 }

这种方式在高并发的情况下也是有问题的,因为在new对象的过程中,这不是一个原子操作,可能发生指令重排,所以会导致对象没有创建完成但是已经赋值,当被别的线程打断,调用的时候就会出现问题,看起来有对象其实没有。所以要避免指令重排

	private static volatile LazyMan lazyMan = null;
8.4单例模式的优缺点

优点:提供了对唯一实例的受控访问并可以节约系统资源

缺点:缺少抽象层而难以扩展

8.5懒汉式单例和饿汉式单例的区别

饿汉式单例是在类加载的时候创建唯一的实例,

懒汉式单例是在第一次调用实例方法时才会创建实例,为了保证线程安全,一般加锁

8.6适用环境

系统只需要一个实例对象,客户调用者调用类的单个实例只允许使用一个公共访问点。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值