设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
常见的设计模式
设计模式的七大原则:
要求:1)七大原则的核心思想 2)能够以类图的说明设计原则 3)在项目实际开发中,你在哪里使用到了ocp(开闭)原则。
设计模式常用的七大原则有:
1)单一职责原则
2)接口隔离原则
3)依赖倒转原则
4)里氏替换原则
5)开闭原则ocp
6)迪米特法则
7)合成复用原则
1、单一职责原则
对类来说的,即一个类应该只负责一项职责。如A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2
代码示例
public class SingleDemo {
public static void main(String[] args) {
Animal animal = new Animal();
animal.run("老虎");
animal.run("狮子");
animal.run("老鹰");
}
}
//定义动物类
class Animal{
//森林奔跑方法
public void run(String animal){
System.out.println(animal + "正在森林里愉快的奔跑");
}
}
问题描述:违反了单一职责原则,"老鹰"不能用奔跑。
解决方法:类拆分成奔跑、飞翔
public class SingleDemo {
public static void main(String[] args) {
ForestAnimal forestAnimal = new ForestAnimal();
forestAnimal.run("老虎");
forestAnimal.run("狮子");
SkyAnimal skyAnimal = new SkyAnimal();
skyAnimal.fly("老鹰");
}
}
class ForestAnimal{
//森林奔跑方法
public void run(String animal){
System.out.println(animal + "正在森林里愉快的奔跑");
}
}
class SkyAnimal{
//森林奔跑方法
public void fly(String animal){
System.out.println(animal + "正在天空上愉快的飞翔");
}
}
问题描述:严格遵守了单一职责原则,也保证了业务逻辑的正确性。但是这样做的话,不仅要拆分类,同时还要大范围的修改客户端(即main方法里的代码也要改动)
解决方法:单一职责原则下沉,让方法单一职责原则。
public class SingleDemo {
public static void main(String[] args) {
Animal animal = new Animal();
animal.runForest("老虎");
animal.runForest("狮子");
animal.runSky("老鹰");
}
}
class Animal {
//森林奔跑方法
public void runForest(String animal) {
System.out.println(animal + "正在森林里愉快的奔跑");
}
//天空飞翔方法
public void runSky(String animal) {
System.out.println(animal + "正在天空上愉快的飞翔");
}
}
问题描述:没有严格遵守单一职责原则。但是这样实现对原有的类改动较小,客户端改动也比较小也能满足业务逻辑。
解决方法:根据具体情况具体分析
注意事项&细节
降低类的复杂度,一个类只负责一项职责
(一个类的职责少了,相应的复杂度就会降低)
提高类的可读性以及可维护性
(相应的复杂度降低,代码量就会减少,可读性也就会提高,可维护性自然就提高了)
降低变更引起的风险
(一个类的职责越多,变更的可能性就更大,变更带来的风险也就越大)
通常情况下,我们应当遵守单一职责原则
(只有逻辑足够简单,才可以在代码级违反单一职责原则,只有类中方法数量足够少,才可以在方法级别保持单一职责原则,参考方案二)
2、接口隔离原则
客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。
代码示例
使用场合,提供调用者需要的方法,屏蔽不需要的方法.满足接口隔离原则.比如说电子商务的系统,有订单这个类,有三个地方会使用到,
一个是用户,只能有查询方法,
一个是外部系统,有添加订单的方法,
一个是管理后台,添加删除修改查询都要用到.
根据接口隔离原则(ISP),一个类对另外一个类的依赖性应当是建立在最小的接口上.
也就是说,对于用户,它只能依赖有一个查询方法的接口.
interface IOrderForPortal{
String getOrder();
}
interface IOrderForOtherSys{
String insertOrder();
String getOrder();
}
interface IOrderForAdmin{ //extendsIOrderForPortal,IOrderForOtherSys
String deleteOrder();
String updateOrder();
String insertOrder();
String getOrder();
}
/*
interface IOrderForPortal{
String getOrder();
}
interface IOrderForOtherSys{
String insertOrder();
}
interface IOrderForAdmin extendsIOrderForPortal,IOrderForOtherSys{
String updateOrder();
String deleteOrder();
}
*/
class Order implements IOrderForPortal,IOrderForOtherSys,IOrderForAdmin{
private Order(){
//--什么都不干,就是为了不让直接 new,防止客户端直接New,然后访问它不需要的方法.
}
//返回给Portal
public static IOrderForPortal getOrderForPortal(){
return (IOrderForPortal)new Order();
}
//返回给OtherSys
public static IOrderForOtherSys getOrderForOtherSys(){
return (IOrderForOtherSys)new Order();
}
//返回给Admin
public static IOrderForAdmin getOrderForAdmin(){
return (IOrderForAdmin)new Order();
}
//--下面是接口方法的实现.只是返回了一个String用于演示
public String getOrder(){
return "implemented getOrder";
}
public String insertOrder(){
return "implemented insertOrder";
}
public String updateOrder(){
return "implemented updateOrder";
}
public String deleteOrder(){
return "implemented deleteOrder";
}
}
public class TestCreateLimit{
public static void main(String[] args){
IOrderForPortal orderForPortal =Order.getOrderForPortal();
IOrderForOtherSys orderForOtherSys =Order.getOrderForOtherSys();
IOrderForAdmin orderForAdmin = Order.getOrderForAdmin();
System.out.println("Portal门户调用方法:"+orderForPortal.getOrder());
System.out.println("OtherSys外部系统调用方法:"+orderForOtherSys.insertOrder());
System.out.println("Admin管理后台调用方法:"+orderForAdmin.getOrder()+";"+orderForAdmin.insertOrder()+";"+orderForAdmin.updateOrder()+";"+orderForAdmin.deleteOrder());
}
}
注意事项
在使用接口隔离原则时我们要控制接口的颗粒度,颗粒度不能太大,也不能太小。如果太小就会造成接口泛滥,不利于维护;接口入如果太大就会违背接口隔离原则,灵活性较差,使用起来不方便。一般来说接口中仅包含某业务模块的方法即可,不应该有其他业务模块的方法。
3、依赖倒转原则
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
注意事项
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
- 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
- 继承时遵循里氏替换原则
4、里氏替换原则
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法
5、开闭原则
- 开闭原则(Open Closed Principle)是编程中最基础、最重要设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
- 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
代码示例
package com.study;
public class Ocp {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收 Shape 对象,然后根据 type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1){
drawRectangle(s);
} else if (s.m_type == 2){
drawCircle(s);
} else if (s.m_type == 3){
drawTriangle(s);
}
}
//绘制矩形
public void drawRectangle(Shape r) {
System.out.println(" 绘制矩形 ");
}
//绘制圆形
public void drawCircle(Shape r) {
System.out.println(" 绘制圆形 ");
}
//绘制三角形
public void drawTriangle(Shape r) {
System.out.println(" 绘制三角形 ");
}
}
//Shape 类,基类
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
//新增画三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 2;
}
}
问题描述
我们需要新增一个三角形,我们就需要新增一个三角形的类,然后还要判断type的值,这样违反了ocp原则,即对扩展开放(提供方),对修改关闭(使用方)。
解决方法
我们给类增加新功能的时候,尽量不修改代码,或者尽可能的少修改代码。使用开闭原则改进
package com.study;
public class Ocp {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收 Shape 对象,然后根据 type,来绘制不同的图形
public void drawShape(Shape s) {
s.draw();
}
}
//Shape 类,基类
abstract class Shape {
abstract void draw();
}
class Rectangle extends Shape {
@Override
void draw() {
System.out.println(" 绘制矩形 ");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println(" 绘制圆形 ");
}
}
//新增画三角形
class Triangle extends Shape {
@Override
void draw() {
System.out.println(" 绘制三角形 ");
}
}
6、迪米特法则
迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),即一个类对自己依赖的类知道的越少越好。
迪米特法则可以降低系统的耦合度,使类与类之间保持松耦合状态。
直接朋友:是指对象与对象之间的耦合关系,包括,依赖,关联,组合,聚合等。其中成员变量,方法参数,方法返回值中的类为直接朋友。
注意事项
- 在类的划分上,应该尽可能的创建松耦合的类,类之间的耦合度越低,复用率越高,一个松耦合的类发生修改不会对关联的类造成太大的影响。
- 在类的结构设计上,每一个类都应当尽可能的降低其成员变量和成员方法的访问权限。
- 在类的设计上,只要有可能,一个类型应当设计成不变类。
- 在对其他类的引用上,一个对象对其他对象的引用应该降到最低。
7、合成复用原则
合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些 已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能 的目的。简言之:复用时要尽量使用组合/聚合关系(关联关系),少用继承。
一、设计模式类型
设计模式分为三种类型,共23种
1)创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
3)行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式
二、单例设计模式八种方式
1)饿汉式(静态常量)
2)饿汉式(静态代码块)
3)懒汉式(线程不安全)
4)懒汉式(线程安全,同步方法)
5)懒汉式(线程安全,同步代码块)
6)双重检查
7)静态内部类
8)枚举
1、饿汉式(静态常量)应用实例
步骤如下:
1)构造器私有化
2)类的内部创建对象
3)向外暴露一个静态公共方法。getInstance
4)代码实现
//饿汉式(静态变量)
class Singleton {
//1.构造器私有化,外部不能new
private Singleton() {
}
//2.本类内部创建对象实力
private final static Singleton instance = new Singleton();
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优缺点说明:
1)优点:写法简单,在类加载之前就实例化。避免了线程同步问题。
2)缺点:在类加载的时候就完成了实例化,会造成内存的浪费
2、饿汉式(静态代码块)
代码演示
//饿汉式(静态变量)
class Singleton {
//1.构造器私有化,外部不能new
private Singleton() {
}
//2.本类内部创建对象实力
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优缺点说明:
1)优点:写法简单,在类加载之前就实例化。避免了线程同步问题。
2)缺点:在类加载的时候就完成了实例化,会造成内存的浪费
3、懒汉式(线程不安全)
代码演示
class Singleton {
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance
//即懒汉式
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
1)懒加载的效果,但是只能在单线程下使用。
2)会出现创建多个实例的情况。
3)结论:在实际开发中,不要使用这种方式
4、懒汉式(线程安全,同步方法)
代码演示
class Singleton {
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全的问题
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
1)解决了线程不安全的问题
2)效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。但是这个方法只执行一次实例化代码就够了。
3)结论:实际开发中,不推荐这种方式
5、懒汉式(线程安全,同步代码块)
代码示例
class Singleton {
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全的问题
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
优缺点说明:
1)这种方式,本意是相对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的代码块
2)但是这种同步并不能起到线程同步的作用。当第一个线程进入if判断,没return之前第二个线程就进去了,会产生多个实例,导致线程不安全。
3)结论:实际开发中,不能使用这种方式。
6、双重检查(推荐使用)
代码示例:
class Singleton {
private static volatile Singleton instance;
public Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优缺点说明:
1)双重检查是多线程开发中常使用的,如代码所示,我们进行了两次if(instance==null)检查,这样就可以保证线程安全。
2)这样,实例化代码只用执行一次,后面再次访问,判断if(instance==null)直接return实例化对象,也避免了反复进行方法同步。
3)线程安全,延迟加载,效率高
4)结论:实际开发中,推荐使用这种单例设计模式
7、静态内部类(推荐使用)
特点:
1)外部类被装载的时候,静态内部类不会被装载。
2)当我们静态内部类,在调用getInstance()时,才会被装载且只会装载一次,线程安全。
代码演示:
class Singleton {
//构造器私有化
private Singleton() {
}
//写一个静态内部类,该类中有一个静态属性Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态公有方法,直接返回SingletonInstance.INSTANCE
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优缺点说明:
1)这种方式采用了类加载的机制来保证初始化实例时只有一个线程。
2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4)优点:避免了线程不安全,懒加载效率高
5)结论:推荐使用
提问:为什么JVM初始化是线程安全的。
答:类的初始化会调用<clinit>()方法,虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都要阻塞等待,知道活动线程执行<clinit>()方法完毕。
8、枚举(推荐使用)
代码示例
enum Singleton {
INSTANCES;
}
优缺点说明:
1)借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
2)懒加载、避免了线程安全问题、避免反序列化破坏问题、避免反射破坏问题。
3)结论:推荐使用
三、简单工厂模式
1、引入
UML图
代码示例
public abstract class Pizza {
protected String name;
public abstract void prepare();
public void bake() {
System.out.println(name + ",is baking;");
}
public void cut() {
System.out.println(name + ",is cutting;");
}
public void box() {
System.out.println(name + ",is boxing");
}
public void setName(String name) {
this.name = name;
}
}
public class PepperPizza extends Pizza {
@Override
public void prepare() {
System.out.println("给胡椒披萨准备原材料");
}
}
public class GreekPizza extends Pizza {
@Override
public void prepare() {
System.out.println(" 给希腊pizza,准备原材料");
}
}
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println(" 给制作奶酪pizza 准备原材料");
}
}
public class OrderPizza {
//构造器
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购pizza的类型是什么
do {
orderType = gettype();
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊pizza");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("奶酪pizza");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒pizza");
} else {
break;
}
// 输出pizza 制作的过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
// 写一个方法可以获取用户希望订购的pizza类
private String gettype() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza();
}
}
问题描述:违反了OCP原则(开闭原则),即对扩展开发,对修改关闭。如果新增一个新增一个pizza类 需要修改客户端
解决方法:使用简单工厂
2、基本介绍
1)简单工厂模式是属于创建型模式,是工厂模式的一种,简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2)简单工厂模式:定义一个创建对象的类,由这个类来封装实例化对象的行为
3)在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂
3、UML图
4、代码实现
public class SimpleFactory {
//根据orderType 返回对应的pizza对象
public Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊pizza");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("奶酪pizza");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒pizza");
}
return pizza;
}
}
public class OrderPizza {
// 定义一个简单工厂对象
SimpleFactory simpleFactory;
Pizza pizza = null;
public OrderPizza(SimpleFactory simpleFactory) {
setFactory(simpleFactory);
}
public void setFactory(SimpleFactory simpleFactory) {
String orderType = "";
this.simpleFactory = simpleFactory;
do {
orderType = getType();
pizza = this.simpleFactory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购pizza失败");
break;
}
} while (true);
}
// 写一个方法可以获取用户希望订购的pizza类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza(new SimpleFactory());
System.out.println("退出程序");
}
}
5、代码示例(使用静态方法实现)
//根据orderType 返回对应的pizza对象
public static Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊pizza");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("奶酪pizza");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒pizza");
}
return pizza;
}
public class OrderPizza {
Pizza pizza = null;
String orderType = "";
public OrderPizza() {
do {
orderType = getType();
pizza = SimpleFactory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购pizza失败");
break;
}
} while (true);
}
// 写一个方法可以获取用户希望订购的pizza类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza();
System.out.println("退出程序");
}
}
四、工厂方法模式
1、引入
一个新需求:
在各个城市的地方点不同口味的披萨
思路1
使用简单工厂,创建不同的简单工厂,这样也可以实现功能,但是软件的维护性、可扩展性并不是特别好
思路2
使用工厂模式
2、UML图
3、代码示例
public abstract class Pizza {
protected String name;
public abstract void prepare();
public void bake() {
System.out.println(name + ",is baking;");
}
public void cut() {
System.out.println(name + ",is cutting;");
}
public void box() {System.out.println(name + ",is boxing");}
public void setName(String name) {
this.name = name;
}
}
public class LDPepperPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦胡椒pizza");
System.out.println("伦敦胡椒pizza 准备原材料");
}
}
public class LDChessPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦奶酪pizza");
System.out.println("伦敦奶酪pizza 准备原材料");
}
}
public class BJPepperPizza extends Pizza {
@Override
public void prepare() {
setName("北京胡椒pizza");
System.out.println("北京胡椒pizza 准备原材料");
}
}
public class BJChessPizza extends Pizza {
@Override
public void prepare() {
setName("北京奶酪pizza");
System.out.println("北京奶酪pizza 准备原材料");
}
}
public class BJOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new BJChessPizza();
} else if (orderType.equals("pepper")) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
public class LDOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new LDChessPizza();
} else if (orderType.equals("pepper")) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
public abstract class OrderPizza {
//定义一个抽象方法,createPizza,让各个工厂子类自己实现
abstract Pizza createPizza(String orderType);
//构造器
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购pizza的类型是什么
do {
orderType = gettype();
pizza = createPizza(orderType); //抽象方法,由工厂子类完成
// 输出pizza 制作的过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
// 写一个方法可以获取用户希望订购的pizza类
private String gettype() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
public class PizzaStore {
public static void main(String[] args) {
String loc = "bj";
if (loc.equals("bj")) {
new BJOrderPizza();
} else if (loc.equals("ld")) {
new LDOrderPizza();
}
}
}
五、抽象工厂模式
1、基本介绍
1)抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
2)抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3)从设计层面看,抽象工厂模式就是对简单工厂模式的改进
4)将工厂抽象成两次,absFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
2、UML图
3、代码示例
//这是一个工厂子类
public class BJFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
System.out.println("使用的是抽象工厂模式");
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new BJChessPizza();
} else if (orderType.equals("pepper")) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
//这是一个工厂子类
public class LDFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
System.out.println("使用的是抽象工厂模式");
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new LDChessPizza();
} else if (orderType.equals("pepper")) {
pizza = new LDChessPizza();
}
return pizza;
}
}
//抽象工厂接口
public interface AbsFactory {
// 让下面的工厂子类来具体的实现
public Pizza createPizza(String orderType);
}
public class OrderPizza {
AbsFactory factory;
public OrderPizza(AbsFactory factory) {
setAbsFactory(factory);
}
private void setAbsFactory(AbsFactory factory) {
Pizza pizza = null;
String orderType = "";
this.factory = factory;
do {
orderType = gettype();
//factory 可能是北京的工厂子类,也可能是伦敦的工厂子类
pizza = factory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购失败");
break;
}
} while (true);
}
// 写一个方法可以获取用户希望订购的pizza类
private String gettype() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza(new BJFactory());
}
}
六、原型模式
1、引入
在Java中创建一个对象的方式有如下几种:
- 通过new关键字调用类的构造函数进行创建;
- 通过实现Cloneable接口重写clone方法进行创建;
- 通过反射调用newInstance进行创建;
- 通过反序列化进行对象的创建;
1、传统方式
- 通过new关键字调用类的构造函数进行创建;
克隆羊问题
现在有一只羊tom,姓名tom,年龄为:1,颜色为:白色,请编写程序创建和tom羊 属性完全相同的10只羊。
1)传统方法解决问题
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Sheep {
private String name;
private int age;
private String color;
}
public class Client {
public static void main(String[] args) {
Sheep sheep1 = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
Sheep sheep3 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
Sheep sheep4 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
Sheep sheep5 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
}
}
优缺点说明:
优点:通俗易懂,操作简单
缺点:1、创建新对象时,一直需要获取对象的属性,效率较低。2、总是需要初始化对象,而不是动态的获取对象运行的状态,不灵活。
改进思路:
Java中Object类是所有类的根类,Object类提供了一个clone()的方法,改方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须实现一个接口Cloneable,该接口表示该类能够复制具有复制的能力=》原型模式。
2、通过实现Cloneable接口重写clone方法进行创建
原型模式-基本介绍
- 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是–种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它
类型对比:
该方式是直接基于原始对象在内存中的二进制流直接复制得到拷贝对象,其内容和原始对象完全一致,对比第一种方式,效率更高,无需重复对属性进行赋值操作。原型模式主要适用如下场景:
- 通过构造器创建对象的成本比较大,比如创建过程中时间、CPU、网络资源占用过多;
- 创建一个对象需要繁琐的数据准备或者权限设置等;
- 系统中需要大量使用该对象的副本,且各个调用者需要给它们各自的副本进行属性重新赋值;
2、代码实现
@Data
public class Person implements Cloneable{
private String name;
private String country;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Slf4j
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Person jack = new Person();
jack.setName("jack");
jack.setCountry("china");
// 原始对象为:Person(name=jack, country=china)
log.info("原始对象为:{}", jack);
Person mason = (Person)jack.clone();
mason.setName("mason");
// 克隆对象为:Person(name=mason, country=china)
log.info("克隆对象为:{}", mason);
// 原始对象为:Person(name=jack, country=china)
log.info("原始对象为:{}", jack);
}
}
这里可以看到,原始对象jack进行clone后,得到的拷贝对象mason是完全复制的其属性的,mason在修改个别属性后,就完成了自己对象的初始化,就可以进行使用了,无论是复制还是修改,效率都比new提升了不少。
需要注意的是,Cloneable接口提供的clone方法,默认实现的是浅拷贝,只有基本数据类型和基本包装类型是完全复制的,对于引用类型则不是完全拷贝的
七、建造者模式
1、引入
盖房子项目需求
1)需要建房子:这一过程为打桩、砌墙、封顶
2)房子有各种各样的,比如普通房子,高楼,别墅,过程一样,但是类型不一样
UML图
传统方法
代码实现
public abstract class AbstractHose {
protected abstract void buildBasic();
protected abstract void buildWalls();
protected abstract void roofed();
public void build() {
buildBasic();
buildWalls();
roofed();
System.out.println("房子建好了");
}
}
public class CommonHouse extends AbstractHose {
@Override
protected void buildBasic() {
System.out.println("给普通房子打地基");
}
@Override
protected void buildWalls() {
System.out.println("给普通房子砌墙");
}
@Override
protected void roofed() {
System.out.println("给普通房子盖屋顶");
}
}
public class Client {
public static void main(String[] args) {
final CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
}
}
优缺点说明:
1)比较简单,容易操作
2)没有设计缓存层对象,程序扩展性和维护性不好,这种设计方案,把产品(房子)和创建产品的过程(建造房子流程)封装在一起,耦合度高
3)解决方案:将产品和建造流程解耦=》建造者模式
2、基本介绍
1)建造者模式又叫生成器模式,是一种构建模式。可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同方法可以构造出不同表现(属性)的对象。
2)建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部具体构建细节
建造者模式的四个角色
- Product(产品角色):一个具体的产品对象,是多个部件组成的复杂对象,由具体建造者来创建其各个零部件。
- Builder(抽象建造者):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- ConcreteBuilder(具体建造者):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- Director(指挥者):它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生成过程。
3、UML图
4、代码演示
1、产品角色Prdouct(house)
@Data
public class House {
private String basic;
private String wall;
private String roofed;
}
2、抽象建造者Builder(HouseBuilder)
// 抽象的建造者
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好,抽象的方法
public abstract void buildBasic();
public abstract void buildWall();
public abstract void roofed();
//建造房子,将产品(房子)返回
public House buildHouse() {
return house;
}
}
3、具体建造者ConcreteBuilder(CommonHouse、HighBuildingHouse)
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("普通房子打地基5米");
}
@Override
public void buildWall() {
System.out.println("普通砌墙10里面");
}
@Override
public void roofed() {
System.out.println("普通房子的屋顶");
}
}
public class HighBuildingHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("高楼打地基100米");
}
@Override
public void buildWall() {
System.out.println("高楼砌墙20cm");
}
@Override
public void roofed() {
System.out.println("高楼透明屋顶");
}
}
4、指挥者Director(HouseDirector)
// 指挥者,这里指定制作流程
public class HouseDirector {
HouseBuilder houseBuilder = null;
//构造器传入
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//通过setter方法传入
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//如何处理建造房子的流程,交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWall();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
5、客户类Client
public class Client {
public static void main(String[] args) {
// 盖普通的房子
CommonHouse commonHouse = new CommonHouse();
HouseDirector houseDirector = new HouseDirector(commonHouse);
// 完成盖房子,返回产品(房子)
final House house = houseDirector.constructHouse();
System.out.println("=============================");
// 盖高楼
HighBuildingHouse highBuildingHouse = new HighBuildingHouse();
houseDirector.setHouseBuilder(highBuildingHouse);
houseDirector.constructHouse();
}
}
5、注意事项和细节
1)客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
2)每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以方便替换具体建造者或增加新的具体建造者,用户使用不同具体建造者即可得到不同的产品对象。
3)可以更加精确地控制产品的创建过程。
4)增加新的具体建造者无需修改原有类库的代码。符合开闭原则
5)建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。因此其使用范围受到一定的限制。
6)如果产品的内部变化复杂,可能会导致需要定义很多具体建造者来实现这个变化,导致系统变得很庞大,因此要考虑是否选择建造者模式。
7)抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关系构建过程,只关心产品有什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品的,它的主要目的是通过组装零配件而产生一个新产品
八、适配器模式
1、引入
每个国家的插座不同,可以使用多功能转换插头(适配器)。
2、适配器基本介绍
1)适配器模式(Adapter Pattern)将某个类的接口转换成客户端的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
将一个接口转换为客户端所期待的接口,从而使两个接口不兼容的类可以在一起工作
2)适配器模式属于结构型模式
3)主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
3、类适配器
3.1、UML图
3.2、代码示例
// 被适配的类
public class Voltage220V {
public int outPut220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src ;
}
}
// 适配接口
public interface Voltage5V {
public int outPut5V();
}
// 适配器类
public class VoltageAdapter extends Voltage220V implements Voltage5V {
@Override
public int outPut5V() {
int srcV = outPut220V();
int dstV = srcV / 44; //转成5V电压
System.out.println("输出5V电压");
return dstV;
}
}
public class Phone {
public void charging(Voltage5V voltage5V) {
if (voltage5V.outPut5V() == 5) {
System.out.println("电压为5V,可以充电");
} else if (voltage5V.outPut5V() > 5) {
System.out.println("电压大于5V,无法充电");
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println("===类适配器模式==");
final Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
注意事项
1)Java是单继承的,所以适配器需要继承src(被适配)类这一点算是个缺点,因为这要求dst(适配的类)必须是接口,有一定的局限性
2)src类的方法在Adapter中都会暴露出来,也增加了使用成本
3)由于继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
4、对象适配器类
4.1、基本介绍
1)将Adapter类作修改,不是继承src类,而是持有src类的示例,以解决兼容性问题。即:持有src类,实现dst类接口,完成src->dst的适配
2)根据“合成复用”原则,在系统中尽量使用关联关系来替代继承关系。
3)对象适配器模式是适配器模式常用的一种
4.2、UML图
4.3、代码示例
// 被适配的类
public class Voltage220V {
public int outPut220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src ;
}
}
// 适配接口
public interface Voltage5V {
public int outPut5V();
}
// 适配器类
public class VoltageAdapter implements Voltage5V {
private Voltage220V voltage220V;//关联关系中的聚合
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int outPut5V() {
int dstV = 0;
if (null != voltage220V) {
int srcV = voltage220V.outPut220V();//获取220V 电压
System.out.println("使用对象适配器,进行适配 ");
dstV = srcV / 44;
System.out.println("适配完成,输出的电压为=" + dstV);
}
return dstV;
}
}
public class Phone {
public void charging(Voltage5V voltage5V) {
if (voltage5V.outPut5V() == 5) {
System.out.println("电压为5V,可以充电");
} else if (voltage5V.outPut5V() > 5) {
System.out.println("电压大于5V,无法充电");
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println("===对象适配器模式==");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
4.4、注意事项
1)对象适配器和类适配器其实算是同一种思想,只不过实现的方式不一样。根据合成复用原则,使用聚合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
2)使用成本低,更加灵活。
5、接口适配器模式
5.1、接口适配器模式介绍
1)接口适配器模式又叫(缺省适配器模式)
2)当不需要全部实现接口提供的方法时,可以先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以有选择地覆盖父类的某些方法来实现需求。
3)适用于一个不想使用其所有的方法的情况。
5.2、代码示例
public interface Interface {
public void m1();
public void m2();
public void m3();
public void m4();
}
//再AbsAdapter 我们将Interface的方法进行默认实现
public abstract class AbsAdapter implements Interface {
public void m1() {
}
public void m2() {
}
public void m3() {
}
public void m4() {
}
}
public class Client {
public static void main(String[] args) {
final AbsAdapter absAdapter = new AbsAdapter() {
// 我们只需要去覆盖我们需要使用的接口方法
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}
}
九、桥接模式
1、引入
手机操作问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网、打电话等)
如图:
传统方案解决手机使用问题
1.1、UML
1.2、传统方案解决手机操作问题分析
- 扩展性问题(类爆炸)如果我们再增加手机的样式(旋转式)就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也需要在各个类下增加。
- 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。
- 解决方案-使用桥接模式
2、桥接模式
2.1、桥接模式的基本介绍
- 桥接模式(Bridge)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及对他们的功能扩展
2.2、桥接模式(Bridge)原理类图
原理类图说明:
1)Client类:桥接模式的调用者
2)抽象类(Abstractation):维护了Implementor/即它的实现类ConcreteImplementorA,二者是聚合的关系,Abstractation充当了桥接类
3)RefinedAbstraction:是Abstractation抽象类的子类
4)Implementor:是行为实现类的接口
5)ConcreteImplementorA:是行为的具体实现类
6)从UML图:这里的抽象类和接口是聚合的关系,其实也是调用者和被调用者的关系
桥接模式解决操作手机问题
2.3、UML图
2.4、代码演示
public abstract class Phone {
//组合品牌
private Brand brand;
public Phone() {
}
//构造器
public Phone(Brand brand) {
this.brand = brand;
}
protected void open() {
brand.open();
}
protected void close() {
brand.close();
}
protected void call() {
brand.call();
}
}
// 接口
public interface Brand {
void open();
void close();
void call();
}
public class Vivo implements Brand {
@Override
public void open() {
System.out.println("Vivo手机开机了!");
}
@Override
public void close() {
System.out.println("Vivo手机关机!");
}
@Override
public void call() {
System.out.println("Vivo手机打电话");
}
}
public class XiaoMi implements Brand {
@Override
public void open() {
System.out.println("小米手机开机了!");
}
@Override
public void close() {
System.out.println("小米手机关机!");
}
@Override
public void call() {
System.out.println("小米手机打电话!");
}
}
//折叠式手机的类,继承了抽象类Phone
public class FoldedPhone extends Phone {
public FoldedPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println("折叠样式手机");
}
public void close() {
super.close();
System.out.println("折叠样式手机");
}
public void call() {
super.call();
System.out.println("折叠样式手机");
}
}
public class UpRightPhone extends Phone {
public UpRightPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println("直立样式手机");
}
public void close() {
super.close();
System.out.println("直立样式手机");
}
public void call() {
super.call();
System.out.println("直立样式手机");
}
}
public class Client {
public static void main(String[] args) {
// 获取折叠式手机(样式+品牌)
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
System.out.println("==========");
final Phone phone2 = new FoldedPhone(new Vivo());
phone2.open();
phone2.call();
phone2.close();
System.out.println("==========");
Phone phone3 = new UpRightPhone(new XiaoMi());
phone3.open();
phone3.call();
phone3.close();
System.out.println("==========");
Phone phone4 = new UpRightPhone(new Vivo());
phone4.open();
phone4.call();
phone4.close();
}
}