一、简单工厂模式。
简单工厂模式一般是学习设计模式的基础。
举个简单的例子:一个客户,想要获取水果(苹果、菠萝、香蕉等),他会去告诉一个水果工厂他想要的水果种类(比如通过名字告诉),然后水果工厂根据名字,生成对应的水果,并返回。
我想要买一个菠萝,我告诉农场我想要的是菠萝,然后,农场给我生成一个菠萝。这就是简单工厂模式的一个例子。
这里有几个点:
1.菠萝:我想获得的产品,具体的产品类。
2.水果:菠萝属于的是水果的一种,这是一个抽象类,或者接口。
3.水果工厂:生成水果的工厂,是一个工厂类。
简单的工厂模式只需要包含上述3个点即可。我们只需要传入指定的参数,就可以获得需要的对象,同时不需要关注他是怎么被创建,也不需要我们手动通过new的方式创建。
来个例子理解一下(就是这么一个流程图):
用一个代码来加深印象:
场景:某公司需要开发一个图表库,包含有3种图表(HistogramChart、PieChart、LineChart)。根据设置不同的参数,需要得到不同的图表,同时还要有可扩展性。
我们来使用简单的工厂就可以。
(1)、Chart接口:代表抽象产品类。
public interface Chart {
public void display();
}
(2)、HistogramChart:具体的产品类之一。
public class HistogramChart implements Chart{
@Override
public void display() {
System.out.println("显示柱状图。");
}
public HistogramChart() {
System.out.println("创建柱状图。");
}
}
(3)、PieChart:具体的产品类之一。
public class PieChart implements Chart{
@Override
public void display() {
System.out.println("显示饼状图。");
}
public PieChart() {
System.out.println("创建饼状图");
}
}
(4)、LineChart:具体的产品类之一。
public class LineChart implements Chart{
@Override
public void display() {
System.out.println("显示折线图。");
}
public LineChart() {
System.out.println("创建折线图。");
}
}
(5)、工厂类。
//工厂类,根据name传递的值生成不同的类
public class ChartFactory {
public static Chart getChart(String name){
Chart chart = null;
if (name.equalsIgnoreCase("histogram")){
chart = new HistogramChart();
System.out.println("初始化柱状图");
} else if (name.equalsIgnoreCase("line")){
chart = new LineChart();
System.out.println("初始化折线图");
} else if (name.equalsIgnoreCase("pie")){
chart = new PieChart();
System.out.println("初始化饼状图");
}
return chart;
}
}
然后我们就可以测试一下:
public class Client {
public static void main(String[] args) {
//Chart chart = ChartFactory.getChart("line");//创建line图
//chart.display();
String chartType = XMLUtil.getChartType();
Chart chart = ChartFactory.getChart(chartType);
chart.display();
}
}
注释掉的2行是直接通过传入名字的方式获取到的对象。我这里采用了另一种方式:从配置文件中读取信息。
建立XMLUtil工具类:
public class XMLUtil {
//用于从配置文件中读取到图表类型,并且返回图表名
public static String getChartType(){
try {
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//SimpleFactoryPattern//config.xml"));
NodeList nl = doc.getElementsByTagName("chartType");
Node classNode = nl.item(0).getFirstChild();
String chartType = classNode.getNodeValue().trim();
return chartType;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
在当前包下创建一个配置文件config.xml:
<?xml version="1.0"?>
<config>
<chartType>histogram</chartType>
</config>
注意一下这里的路径一定要写对:
"src//SimpleFactoryPattern//config.xml" src包下的SimpleFactoryPattern(包名)的config.xml
测试一下,运行Client的主方法:
那么简单的工厂模式就是这样了。那么不难发现有一些缺点:
1.工厂类的任务过多,代码过于臃肿。我们在设计程序的时候,尽量要避免臃肿的程序,尽量的使代码分散,达到解耦的效果,这样也有利于后期的维护。
2.想要增加新的实现类,必须要到工厂类中修改代码,比较的麻烦。
那就可以用到下面新的设计模式了。
二、工厂方法模式
前面的简单工厂模式,一个工厂类就承担了所有职责,导致一个工厂负责创建全部的具体类,过于臃肿,工厂方法改进此窘况。
注意到了,在上面的简单工厂模式里,如果我要增加新的产品呢?
这样直接修改工厂类的做法违背了开闭原则。于是我们换一种思路完成:
增加了新的一层:具体按钮工厂。
把原来的工厂类抽象化,只定义通用的方法,让具体的工厂类去实现。这样在添加一个实现时,只需要在创建一个具体类来实现抽象工厂就可以了。
比如以下的场景:某系统运行行车记录仪(Logger)可以采取多种方式进行(FileLogger、DatabaseLogger),通过修改配置,可以轻松的切换记录仪的种类。
我们用工厂方法模式试试:
(1)、创建产品的抽象类(接口)Logger:
public interface Logger {//抽象的商品接口
public void writeLog();
}
(2)、创建他的2个实现类FileLogger、DatabaseLogger:
public class FileLogger implements Logger{
@Override
public void writeLog() {
System.out.println("文件日志记录");
}
}
public class DatabaseLogger implements Logger {
@Override
public void writeLog() {
System.out.println("数据库日志记录");
}
}
(3)、设计一个日志接口,作为抽象工厂:
//抽象的工厂接口,定义一个通用的工厂方法
public interface LoggerFactory {
public Logger creatLogger();//抽象的建立商品的方法,具体交给实现类来完成
}
(4)、设计具体的工厂类:
public class FileLoggerFactory implements LoggerFactory{
@Override
public Logger creatLogger() {
Logger logger = new FileLogger();
return logger;
}
}
(5)、测试一下
public class Client {
public static void main(String[] args) {
LoggerFactory factory;
Logger logger;
factory = new FileLoggerFactory();
logger = factory.creatLogger();
logger.writeLog();
}
}
采用此方法的话,通过new的方式,指定一个类型,构造出该类型的工厂类,在通过此工厂类来创建具体的产品。
换一种方法呢?
public class Client {
public static void main(String[] args) {
// LoggerFactory factory;
// Logger logger;
// factory = new FileLoggerFactory();
// logger = factory.creatLogger();
// logger.writeLog();
LoggerFactory factory = (LoggerFactory) XMLUtil.getObjectBean();
Logger logger = factory.creatLogger();
logger.writeLog();
}
}
这样我们就可以在配置文件中进行参数的传递了。同样的:
配置类:
<?xml version="1.0"?>
<config>
<className>FactoryPattern.DatabaseLoggerFactory</className>
</config>
XMLUtil中声明新的方法,用于通过className获取对应的实例:
public static Object getObjectBean(){
try {
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//FactoryPattern//config.xml"));
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = classNode.getNodeValue();
Class<?> c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
也要注意路径的写法别搞错了。
来试一试:
还真成了。
三、抽象工厂模式
抽象工厂模式,和上面的工厂方法模式,还是有一点相似的。体现在:角色方面。二者都是分成了:抽象工厂类,具体工厂类,抽象产品类,具体产品类。但是还是有差别的,我画个图:
有点凌乱。不过你可以这么理解:不同抽象产品的实现类之间是有一定联系的。举个例子:抽象产品A是空调,具体产品A1是美的空调,A2是格力空调;B是冰箱,B1是美的冰箱,B2是格力冰箱。工厂1是美的工厂,2是格力工厂。这么一来好理解了吧。
来一个具体的场景呢?
(1)、按钮接口,充当抽象产品类:
public interface Button {
public void display();
}
按钮接口的2个具体实现类:
//具体的产品
public class SpringButton implements Button{
@Override
public void display() {
System.out.println("显示浅绿色按钮。");
}
}
public class SummerButton implements Button{
@Override
public void display() {
System.out.println("显示浅蓝色按钮。");
}
}
(2)、文本框接口,也是抽象产品类;
//文本框接口
public interface TextFiled {
public void display();
}
具体实现:
public class SpringTextFiled implements TextFiled{
@Override
public void display() {
System.out.println("显示浅绿色文本框。");
}
}
public class SummerTextFiled implements TextFiled{
@Override
public void display() {
System.out.println("显示浅蓝色文本框。");
}
}
(3)、组合框接口:
//组合框接口,充当抽象产品
public interface ComboBox {
public void display();
}
具体实现:
public class SpringComboBox implements ComboBox{
@Override
public void display() {
System.out.println("显示浅绿色组合框。");
}
}
public class SummerComboBox implements ComboBox{
@Override
public void display() {
System.out.println("显示浅蓝色组合框。");
}
}
(4)、抽象的工厂类:
//皮肤工厂接口,充当总的工厂接口
public interface SkinFactory {
public Button createButton();
public TextFiled createTextFiled();
public ComboBox createComboBox();
}
(5)、具体的工厂:
public class SpringSkinFactory implements SkinFactory{
@Override
public Button createButton() {
return new SpringButton();
}
@Override
public TextFiled createTextFiled() {
return new SpringTextFiled();
}
@Override
public ComboBox createComboBox() {
return new SpringComboBox();
}
}
public class SummerSkinFactory implements SkinFactory{
@Override
public Button createButton() {
return new SummerButton();
}
@Override
public TextFiled createTextFiled() {
return new SummerTextFiled();
}
@Override
public ComboBox createComboBox() {
return new SummerComboBox();
}
}
写完了代码。来测试一下,比如我想得到一套夏天的皮肤:
那我配置文件就要这样写:
<?xml version="1.0"?>
<config>
<className>AbstractFactoryPattern.SummerSkinFactory</className>
</config>
然后测一测:
public class Client {
public static void main(String[] args) {
SkinFactory factory;
Button button;
TextFiled textFiled;
ComboBox comboBox;
SkinFactory skinFactory = (SkinFactory)XMLUtil.getObjectBean();
button = skinFactory.createButton();
textFiled = skinFactory.createTextFiled();
comboBox = skinFactory.createComboBox();
button.display();
textFiled.display();
comboBox.display();
}
}
结果:
四、单例模式
前面几种工厂模式介绍完了,现在到单例模式。单例模式也是一个应用非常广泛的模式了,spring中的bean默认就是单例的。
单例模式的实现可以分成有:饿汉式、懒汉式。
饿汉式:只要类一进行加载,这个唯一的实例就会被创建。代码实现:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();//直接在生成类的时候创建好
public EagerSingleton() {
}
public static EagerSingleton getInstance(){
return instance;
}
}
注意这个instance单例,要用到static修饰,代表着是随着类而加载的;还要有个final,表示这个单例不可以再被修改。
看看到底是不是单例:
public class Test {
public static void main(String[] args) {
EagerSingleton i1 = EagerSingleton.getInstance();
EagerSingleton i2 = EagerSingleton.getInstance();
System.out.println(i1 == i2);
// LazySingleton l1 = LazySingleton.getInstance();
// LazySingleton l2 = LazySingleton.getInstance();
// System.out.println(l1 == l2);
}
}
结果:
发现了两次调用获取实例方法,获得的对象是同一个(指向了同一个地址)。
懒汉式:什么意思呢?这个类比较懒,只有当你需要获取实例的时候,才进行创建:
public class LazySingleton {
private static LazySingleton instance = null;
public LazySingleton() {
}
public static LazySingleton getInstance(){
if (instance == null){
synchronized (LazySingleton.class){
instance = new LazySingleton();//懒汉式:在调用方法时才进行创建,注意要用同步代码块包装起来。
}
}
return instance;
}
}
这里面用同步代码块包裹起来,是怕同时有两个线程调用getInstance的时候,会出现创建了2个实例,不信我们模拟一下:
public class ThreadSingleInstanceTest {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
}
static class ThreadA extends Thread{
@Override
public void run() {
while (true){
LazySingleton i1 = LazySingleton.getInstance();
System.out.println(currentThread().getName() + i1);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class ThreadB extends Thread{
@Override
public void run() {
while (true){
LazySingleton i2 = LazySingleton.getInstance();
System.out.println(currentThread().getName() + i2);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
首先看看加了同步的情况:
输出的地址都是一样的,单例无疑了。
我把synchronized去掉呢?
不对劲了,所以要加上同步代码块,保证唯一性。
如果某一瞬间,线程A,B都通过了instance == null的判断,然后A先拿到锁,创建了一个实例;B后拿到锁,也创建另一个实例。这样单例模式就被破坏。这时候我们就可以采用双层检查锁定的模式创建单例:
public class DoubleCheckLockingSingleton {
//双层检查锁实现的懒汉单例模式,绝对安全。
private static volatile DoubleCheckLockingSingleton instance = null;
//volatile保证多线程正确处理
public static DoubleCheckLockingSingleton getInstance(){
if (instance == null){//第一层检查
synchronized (DoubleCheckLockingSingleton.class){
if (instance == null){//第二层判断
instance = new DoubleCheckLockingSingleton();
}
}
}
return instance;
}
}
还可以通过静态内部类的方式创建一个单例:
public class InnerClassSingleton {
//静态内部类,只会随着类加载执行一次。保证单例
private static class Inner{
private static final InnerClassSingleton instance = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance(){
return Inner.instance;//返回静态内部类中的实例。
}
}
这样也是比较安全的,而且代码还比较简洁。
总结:单例模式适用于:只需要一个对象,或者只允许存在一个对象的情况。
五、建造者模式
我来画一张流程图:
这里有4个部分组成,解释一下:
Builder:抽象建造者。这里面只需要定义创建一个个零件的方法即可,注意还需要定义一个方法,用来返回创建结果Result。
ConcreteBuilder:具体的建造者。对Builder的具体实现。他具体实现了如何创建一个个零件的方法,并且返回创建的具体结果。
Product:被Builder创建出来的产品。
Dirctor:导演类。负责安排复杂对象的构建顺序,一般用户与Dirctor沟通即可获得构建结果。
比如以下的场景:我想构建不同的角色,比如天使,魔鬼,战神,每个不同角色的构建步骤大同小异。并且要在未来便于我新增新的角色的构建。这样的话我们就可以用建造者模式来实现。
(1)、Actor复杂对象类的编写:
//Actor类,充当复杂对象,待建造,需要建造者建造。
public class Actor {
private String type;
private String sex;
private String face;
private String costume;
private String hairstyle;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getFace() {
return face;
}
public void setFace(String face) {
this.face = face;
}
public String getCostume() {
return costume;
}
public void setCostume(String costume) {
this.costume = costume;
}
public String getHairstyle() {
return hairstyle;
}
public void setHairstyle(String hairstyle) {
this.hairstyle = hairstyle;
}
}
(2)、抽象建造者:
//抽象类ActorBuilder,充当抽象建造者,具体功能还需要实现类来完成
public abstract class ActorBuilder {
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 creatActor(){
return actor;
}
}
(3)、具体的建造者:
//天使创建者,充当了具体建造者
public class AngleBuilder 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 DevilBuilder 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 HeroBuilder 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("茂密");
}
}
(4)、角色控制器,充当指挥者
//角色控制器,充当指挥者
public class ActorController {
//构建复杂产品对象
public Actor construct(ActorBuilder ab){
Actor actor;
ab.buildType();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
ab.buildSex();
actor = ab.creatActor();
return actor;
}
}
(5)、配置文件。我想构建天使角色
<?xml version="1.0"?>
<config>
<className>BuilderPattern.AngleBuilder</className>
</config>
(6)、来测试看看:
public class Client {
public static void main(String[] args) {
ActorBuilder ab;//创建一个建造者
ab = (ActorBuilder)XMLUtil.getObjectBean();
ActorController actorController = new ActorController();
Actor actor = actorController.construct(ab);
System.out.println(actor.getSex());
System.out.println(actor.getCostume());
System.out.println(actor.getFace());
System.out.println(actor.getHairstyle());
System.out.println(actor.getType());
}
}
结果: