尚硅谷Scala (17)

十七、设计模式

17.1 学习设计模式的必要性

1) 面试会被问,所以必须学
2) 读源码时看到别人在用,尤其是一些框架大量使用到设计模式,不学看不懂源码为什么这样写, 比如 Runtime 的单例模式 .
3) 设计模式能让专业人之间交流方便
4) 提高代码的易维护
5) 设计模式是编程经验的总结,我的理解: 即通用的编程应用场景的模式化,套路化 (站在软件
设计层面思考)。

17.2 设计模式的介绍

1) 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式【设计,思想】 不是代码,而是某类问题的通用解决方案,设计模式(Design pattern )代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
2) 设计模式的本质提高 软件的维护性,通用性和扩展性,并降低软件的复杂度【软件巨兽 = 》软
件工程】。
3) << 设计模式 >> 是经典的书,作者是 Erich Gamma Richard Helm Ralph Johnson John
Vlissides Design (俗称 “四人组 GOF ”)
4) 设计模式并不局限于某种语言, java php c++ 都有设计模式 .

17.3 设计模式类型

设计模式分为三种类型,共 23
1) 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
2) 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
3) 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter 模式)、状态模式、策略模式、职责链模式 ( 责任链模式 ) 、访问者模式。

17.4 简单工厂

17.4.1 基本介绍

1) 简单工厂模式是属于创建型模式,但不属于 23 GOF 设计模式之一。简单工厂模式是由一个
工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2) 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为 ( 代码 )
3) 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式 .

17.4.2 看一个具体的需求

看一个披萨的项目:要便于披萨种类的扩展,要便于维护,完成披萨订购功能。

17.4.3 使用传统的方式来完成 

17.4.4 传统的方式的优缺点

1) 优点是比较好理解,简单易操作。
2) 缺点是违反了设计模式的 ocp 原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的
时候,尽量不修改代码,或者尽可能少修改代码 .
3) 比如我们这时要新增加一个 Pizza 的种类 (Cheese 披萨 ) ,我们需要做如下修改

17.4.5 使用简单工厂模式-进行改进

改进的思路分析
分析: 修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意
味着,也需要修改,而创建 Pizza 的代码, 往往有多处
思路: 把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改
该类就可, 其它有创建到 Pizza 对象的代码就不需要修改了 .-> 简单工厂模式

1) 简单工厂模式的设计方案 : 定义一个实例化 Pizaa 对象的类,封装创建对象的代码。
2) 看代码示例

 简单工厂:

object SimpleFactory {

  //提供了一个创建Pizza的方法,有需要创建Pizza调用该方法即可
  def createPizza(t:String):Pizza={
    var pizza:Pizza = null
    if (t.equals("greek")) {
      //创建pizza
      pizza = new GreekPizza
    } else if (t.equals("pepper")) {
      //创建pizza
      pizza = new PepperPizza
    }else if(t.equals("cheese")){
      pizza =new CheesePizza
    }
    return pizza
  }
}
class OrderPizza {
  var orderType: String = _
  var pizza: Pizza = _

  breakable {
    do {
      print("请输入pizza的类型 使用了简单工厂模式:")
      orderType = StdIn.readLine()
      pizza=SimpleFactory.createPizza(orderType)
      if(pizza == null){
        break()
      }
      this.pizza.prepare()
      this.pizza.cut()
      this.pizza.bake()
      this.pizza.box()
    } while (true)
  }
}

17.5 工厂方法模式

17.5.1 看一个新的需求

披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza 、北京的胡
pizza 或者是伦敦的奶酪 pizza 、伦敦的胡椒 pizza

17.5.2 思路 1

使用简单工厂模式,创建不同的简单工厂类,比如 BJPizzaSimpleFactory LDPizzaSimpleFactory 等等. 从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好的方法

17.5.3 思路 2

使用工厂方法模式

17.5.4 工厂方法模式介绍

1) 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中
具体实现。
2) 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。 工厂方法模式将
对象的实例化推迟到子类

17.5.5 工厂方法模式应用案例

披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza 、北京的胡
pizza 或者是伦敦的奶酪 pizza 、伦敦的胡椒 pizza

17.6 抽象工厂模式

17.6.1 基本介绍

1) 抽象工厂模式:定义了一个 trait 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
2) 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3) 从设计层面看,抽象工厂模式就是对简单工厂模式的改进 ( 或者称为进一步的抽象 )
4) 将工厂抽象成两层, AbsFactory( 抽象工厂 ) 具体实现的工厂子类 。程序员可以根据创建对
象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。

17.6.2 抽象工厂模式应用实例

17.7 工厂模式的小结

1) 工厂模式的意义
实例化对象的代码提取出来,放到一个类中统一管理和维护 ,达到和主项目的 依赖关系的解耦
从而提高项目的扩展和维护性。
三种工厂模式
2) 设计模式的依赖抽象原则
创建对象实例时,不要直接 new , 而是把这个 new 类的动作放在一个工厂的方法中,并返回。
也有的书上说,变量不要直接持有具体类的引用。
不要让类继承具体类,而是继承抽象类或者是 trait (接口)
不要覆盖基类中已经实现的方法。

17.8 单例模式

17.8.1 什么是单例模式

单例模式是指:保证在整个的软件系统中,某个类只能存在一个对象实例。

17.8.2 单例模式的应用场景

比如 Hibernate SessionFactory ,它充当数据存储源的代理,并负责创建 Session 对象。 SessionFactory
并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式。 Akka [ActorySystem 单例]

17.8.3 单例模式-懒汉式

object TestSingleTon {
  def main(args: Array[String]): Unit = {
    val instance1 = SingleTon.getInstance
    val instance2 = SingleTon.getInstance
    if(instance1==instance2){
      println("相等") //相等
    }
  }
}

//将SingleTon的构造方法私有化
class SingleTon private(){}

//懒汉式
object SingleTon{
  private var s:SingleTon =null

  def getInstance={
    if(s==null){
      s=new SingleTon
    }
    s
  }
}

17.8.4 单例模式-饿汉式

object TestSingTon2 {
  def main(args: Array[String]): Unit = {
    val instance1 = SingleTon2.getInstance
    val instance2 = SingleTon2.getInstance
    if(instance1==instance2){
      println("相等") //相等
    }
  }
}

//将SingleTon的构造方法私有化
class SingleTon2 private(){}

//饿汉式
object SingleTon2{
  private val s:SingleTon2 = new SingleTon2
  def getInstance={
    s
  }
}

17.9 装饰者模式(Decorator)

17.9.1 看一个项目需求

咖啡馆订单系统项目(咖啡馆):
1) 咖啡种类 / 单品咖啡: Espresso( 意大利浓咖啡 ) ShortBlack LongBlack( 美式咖啡 ) Decaf( 无因 咖啡)
2) 调料: Milk Soy( 豆浆 ) Chocolate
3) 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
4) 使用 OO 的来计算不同种类咖啡的费用 : 客户可以点单品咖啡,也可以单品咖啡 + 调料组合

17.9.2 方案 1-较差的方案

 

17.9.3 方案 1-小结和分析

1) Drink 是一个抽象类,表示饮料
2) description 就是描述,比如咖啡的名字等
3) cost 就是计算费用,是一个抽象方法
4) Decaf 等等就是具体的单品咖啡,继承 Drink, 并实现 cost 方法
5) Espresso&&Milk 等等就是单品咖啡 + 各种调料的组合 , 这个会很多 ..
6) 这种设计方式时 ,会有很多的类,并且当增加一个新的单品咖啡或者调料时,类的数量就会倍
( 类爆炸

17.9.4 方案 2-好点的方案

前面分析到方案 1 因为咖啡单品 + 调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性( 如图 )=> 同时违反 ocp
ocp(开闭原则):通过增加新的类,来扩展功能,开闭原则:开指的是功能扩展开放,闭:不要去修改已有的代码

17.9.5 装饰者模式原理

1) 装饰者模式就像打包一个快递
主体:比如:陶瓷、衣服 (Component)
包装:比如:报纸填充、塑料泡沫、纸板、木板 (Decorator)
2) Component
主体:比如类似前面的 Drink
3) ConcreteComponent Decorator
        ConcreteComponent:具体的主体,
        比如前面的各个单品咖啡
4) Decorator: 装饰者,比如各调料 .
在如图的 Component ConcreteComponent 之间,如果 ConcreteComponent 类很多 , 还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类

17.9.6 装饰者模式定义

1) 装饰者模式: 动态的将新功能附加到对象上 。在对象功能扩展方面,它比 继承更有弹性 ( 递归 )
装饰者模式也体现了开闭原则 (ocp)
2) 这里提到的动态的将新功能附加到对象和 ocp 原则,在后面的应用实例上会以代码的形式体现。

17.9.7 用装饰者模式设计重新设计的方案

 

 17.9.8 装饰者模式下的订单:2 份巧克力+一份牛奶的 LongBlack

 17.9.9 装饰者模式咖啡订单项目应用实例

 在JAVA中:

 

17.10观察者模式(Observer)

17.10.1 看一个项目需求

气象站项目,具体要求如下:
1) 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去 ( 比如发布到自己的网
)
2) 需要设计开放型 API ,便于其他第三方公司也能接入气象站获取数据。
3) 提供温度、气压和湿度的接口
4) 测量数据更新时,要能实时的通知给第三方

17.10.2 WeatherData

通过对气象站项目的分析,我们可以初步设计出一个 WeatherData

 

17.10.3 气象站设计方案 1-普通方案

普通方案的分析 - 发现问题
1) 其他第三方公司接入气象站获取数据的问题
2) 无法在运行时动态的添加第三方
3) 同时违反 ocp 的原则

17.10.4 观察者模式原理

观察者模式类似订牛奶业务
      1) 奶站 / 气象局: Subject
      2) 用户 / 第三方网站: Observer
  Subject :登记注册、移除和通知
      1) registerObserver 注册
      2) removeObserver 移除
      3) notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,
也可能是实施推送,看具体需求定
Observer :接收输入

 

 

 

观察者模式: 对象之间多对一依赖 的一种设计方案,被依赖的对象为 Subject ,依赖的对象为
Observer Subject 通知 Observer 变化 , 比如这里的奶站是 Subject ,是 1 的一方。用户时 Observer , 是多的一方。

17.10.5 气象站设计方案 2-观察者模式

 

 

 

17.10.6 Java 内置观察者模式

说明 :
通过 getXxx 方法,可以让第三方公司接入,并得到相关信息 .
当数据有更新时,气象站通过调用 dataChange() 去更新数据,当第三方再次获取时,就能得到最
新数据,当然也可以推送。

17.11代理模式(Proxy)

17.11.1 代码模式的基本介绍

1) 代理模式: 为一个对象提供一个替身 ,以控制对这个对象的访问
2) 被代理的对象可以是 远程对象 创建开销大的对象 或需要 安全控制的 对象 ( 动态代理 )
3) 代理模式有不同的形式 ( 比如 远程代理,静态代理,动态代理 ) ,都是为了控制与管理对象访问

17.11.2 看一个项目需求

糖果机项目,具体要求如下:
1) 某公司需要将销售糖果的糖果机放置到本地 ( 本地监控 ) 和外地 ( 远程监控 ) ,进行糖果销售。
2) 给糖果机插入硬币,转动手柄,这样就可以购买糖果。
3) 可以监控糖果机的状态和销售情况。

17.11.3 完成监控本地糖果机

对本地糖果机的状态和销售情况进行监控,相对比较简单,完成该功能

 

17.11.4 完成监控远程糖果机

说明 : 对远程糖果机的状态和销售情况进行监控,相对麻烦些,我们先分析一下
1) 方式 1 :因为远程糖果机不在本地,比如在另外的城市,国家,这时可以使用 socket 编程 来进
行网络编程控制 ( 缺点:麻烦 )
2) 方案 2 :在远程放置 web 服务器,通过 web 编程来实现远程监控。
3) 方案 3 :使用 RMI(Remote Method Invocation) 远程方法调用 来完成对远程糖果机的监控,因为RMI 将 socket 的底层封装起来,对外提供调用方法接口即可,这样比较简单,这样我们就可以实现远程代理模式开发。

17.11.5 远程代理模式监控方案

远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。
远程代理通过网络和真正的远程对象沟通信息。

 

17.11.6 Java RMI 实现远程代理

RMI 指的是远程方法调用 (Remote Method Invocation) 。它是一种 机制 ,能够让在 某个 Java 虚拟
机上的对象调用另一个 Java 虚拟机中的对象上的方法 。可以用此方法调用的任何对象必须实现该远程接口,RMI 可以将底层的 socket 编程封装,简化操作。 ( 如图 )

 

17.11.7 Java RMI 的介绍

1) RMI 远程方法调用是计算机之间通过网络实现对象调用的一种通讯机制。
2) 使用 RMI 机制,一台计算机上的对象可以调用另外 一台计算机上的对象来获取远程数据。
3) RMI 被设计成一种 面向对象开发方式 ,允许程序员使用远程对象来实现通信

17.11.8 Java RMI 的开发应用案例-说明

请编写一个 JavaRMI 的案例,代理端 ( 客户端 ) 可以通过 rmi 远程调用 远程端注册的一个服务的
sayHello 的方法,并且返回结果。

17.11.9 Java RMI 的开发应用案例-开发步骤

1) 制作远程接口:接口文件
2) 远程接口的实现: Service 文件
3) RMI 服务端注册,开启服务
4) RMI 代理端通过 RMI 查询到服务端,建立联系,通过接口调用远程方法

17.11.10 Java RMI 的开发应用案例-程序框架

17.11.11 Java RMI 的开发应用案例-代码实现 

 

17.11.12 使用远程代理模式完成远程糖果机监控

 

 

17.11.13 动态代理

动态代理:运行时动态的创建代理类 ( 对象 ) ,并将方法调用转发到指定类 ( 对象 )
动态代理调用的机制图
1) Proxy InvocationHandler 组合充当代理的角色 .
2) RealSubject 是一个实际对象,它实现接口 Subject
3) 在使用时,我们不希望直接访问 RealSubject 的对象,比如:我们对这个对象的访问是有控制的
4) 我们使用动态代理,在程序中通过动态代理创建 RealSubject ,并完成调用 .
5) 动态代理可以根据需要,创建多种组合
6) Proxy 也会实现 Subject 接口的方法,因此,使用 Proxy+Invocation 可以完成对 RealSubject 的动态调用。
7) 但是通过 Proxy 调用 RealSubject 方法是否成功,是由 InvocationHandler 来控制的。 (这里其实就是保护代理)
8) 理解: 创建一个代理对象 代被调用的真实对象 ,使用 反射实现控

17.11.14 动态代理的应用案例

应用案例说明
有一个婚恋网项目,女友 / 男友有个人信息、兴趣爱好和总体评分 , 要求:
1) 不能自己给自己评分
2) 其它用户可以评分,但是不能设置信息,兴趣爱好。
3) 请使用动态代理实现保护代理的效果。
4) 分析这里我们需要写两个代理。一个是自己使用,一个是提供给其它用户使用。
5) 示意图画出 :

17.11.15 几种常见的代理模式介绍— 几种变体

1) 防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。
2) 缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok, 如果取不到资源,再到公网或者数据库取,然后缓存。
3) 静态代理
静态代理通常用于对原有业务逻辑的扩充。
比如持有第二方包的某个类,并调用了其中的某些方法。比如记录日志、打印工作等。可以创建一个代理类实现和第二方方法相同的方法,通过让代理类持有真实对象,调用代理类方法,来达到增加业务逻辑的目的。
4) Cglib 代理
使用 cglib[Code Generation Library] 实现动态代理,并不要求委托类必须实现接口,底层采用 asm 字节码生成框架生成代理类的字节码。
5) 同步代理
主要使用在多线程编程中,完成多线程间同步工作

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值