设计模式使用原则
-
单一职责原则:类的职责应该单一,一个方法只做一件事,这里就不多说了,目的:提高可读性。
-
开闭原则:程序对扩展开放,对修改关闭:当你的程序需要扩展时,不应该修改原来的代码,而是可以添加一个类来扩展。目的:降低维护风险。
-
里氏替换原则:所有引用基类的地方,必须能够使用其子类直接替换,换句话说,所有引用基类的地方必须透明地使用其子类对象。目的:防止继承带来的问题。
-
依赖倒置原则:高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。目的:利于代码升级。
-
接口隔离原则:类不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。目的:使功能解耦,高内聚、低耦合。
-
迪米特法则:一个对象应该对其他对象有最少的了解。最小知识原则对类的低耦合提出了明确的要求。目的:自己做自己的事情。
设计模式分类
23种设计模式分为三类:
-
创建型模式
-
结构型模式
-
行为型模式
顾名思义,创建型 就是怎么创建对象的。结构型就是对象与对象的关系,变成更大的结构。行为型 就是运行时复杂流程的 控制。
创建型模式
单例模式
单例模式的适用场景
单例模式是用来保证系统中某个资源或对象全局唯一的一种模式,比如系统的线程池、日志收集器等。它保证了资源在系统中只有一份,一方面节省资源,另一方面可以避免并发时的内存安全问题。
单例模式的优缺点
优点
单例类只有一个实例,节省了内存资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;
缺点
单例模式一般没有接口,扩展的话除了修改代码基本上没有其他途径。
单例的几种实现方式
饿汉式
csharp
//java
public class SingletonJava {
private static SingletonJava singletonJava = new SingletonJava();
private SingletonJava(){
}
public static SingletonJava getInstance(){
return singletonJava;
}
}
//kotlin
object SingletonKt {
var int = 0
}
饿汉式单例在Kt中只需要用object就可以实现
饿汉模式是在类加载的时候直接创建了对象,不存在线程安全问题,它的优点是简单方便,缺点是占用资源
饿汉式适用于占用资源少,在初始化时就会被用到的类
懒汉式
kotlin
// java
public class SingletonJava {
private static SingletonJava singletonJava;
private SingletonJava(){
}
public static SingletonJava getInstance(){
if (singletonJava == null){
singletonJava = new SingletonJava();
}
return singletonJava;
}
}
// kotlin
class SingletonKt private constructor(){
companion object{
private var instance : SingletonKt? = null
get() {
if (field == null){
field = SingletonKt()
}
return field
}
fun get() : SingletonKt?{
return instance
}
}
}
此处使用get方法,而不是getInstance,是因为在伴生对象声明时,内部已有getInstance方法,会提示方法名重复
与饿汉模式相比,懒汉式有线程安全问题,但可以在实例化过程中通过外部传递参数的形式控制实例化对象
线程安全的懒汉式
kotlin
// java
public class SingletonJava {
private volatile static SingletonJava singletonJava;
private SingletonJava(){
}
public static synchronized SingletonJava getInstance(){
if (singletonJava == null){
singletonJava = new SingletonJava();
}
return singletonJava;
}
}
// kotlin
class SingletonKt private constructor(){
companion object{
private var instance : SingletonKt? = null
get() {
if (field == null){
field = SingletonKt()
}
return field
}
@Synchronized
fun get() : SingletonKt{
return instance!!
}
}
}
相比于普通的懒汉式,kotlin实现在get方法出增加了@Synchronized注解
与饿汉模式相比,几乎适用于所有情况,但复杂度上升。
双重校验
csharp
// java
public class SingletonJava {
private static SingletonJava singletonJava;
private SingletonJava(){
}
public static synchronized SingletonJava getInstance(){
if (singletonJava == null){
synchronized (SingletonJava.class){
if (singletonJava == null){
singletonJava = new SingletonJava();
}
}
}
return singletonJava;
}
}
// kotlin
class SingletonKt private constructor(){
companion object{
val instance : SingletonKt by lazy {
SingletonKt()
}
}
}
Java中会使用到volatile防止指令重排
kotlin中的双重校验借用到了lazy属性
静态内部类实现
kotlin
//Java实现
public class SingletonDemo {
private static class SingletonHolder{
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
return SingletonHolder.instance;
}
}
//kotlin实现
class SingletonDemo private constructor() {
companion object {
val instance = SingletonHolder.holder
}
private object SingletonHolder {
val holder= SingletonDemo()
}
}
枚举类
kotlin
enum class Singleton{
INSTANCE
}
enum可以像一个正常的class一样定义属性和方法,可以实现接口,但不能继承。
虽然它不复杂,且无法通过反射搞破坏,但它无法适用于更广泛的情况。
如果单例模式可能被破坏,可以使用枚举单例模式。
单例与static的选择
- 不需要依赖于其它类或资源时,用静态方法,如果需要依赖其它类的实例,或者需要某些资源时,用单例。
- 如果逻辑有需要重载的可能性,那么用单例。如果肯定不会重载,用静态方法。
- 如果是业务相关逻辑,绝对不用静态方法。如果是业务无关的(绝对无关的),那么可以用静态。
简单工厂模式
简单工厂模式(Simple Factory Pattern)又叫静态工厂方法模式(Static FactoryMethod Pattern),是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
简单工厂模式不属于设计模式,是一种编码的习惯。
使用场景
工厂类负责创建的对象比较少。
客户端只知道传入工厂类的参数,对于如何创建对象并不关心。
实现方式
kotlin
interface ICode {
fun coding()
}
class CodeImplIOS : ICode{
override fun coding() {
Log.e("ln","输出IOS代码")
}
}
class CodeImplAndroid : ICode{
override fun coding() {
Log.e("ln","输出Android代码")
}
}
class CodingFactory {
companion object {
fun getCoding(type: String): ICode {
val code: ICode
if (type.equals("android")) {
code = CodeImplAndroid()
} else {
code = CodeImplIOS()
}
return code
}
}
}
用户只需要传一个type进去,就可以获取到一个ICode对象,然后调用coding方法,就可以打印输出IOS代码或者输出Android代码,不需要关心这个ICode对象是如何生成的
简单工厂模式的优缺点
优点
将对象的创建交给专门的工厂类负责,实现了对象的创建和对象的使用分离。
缺点:
- 工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码
- 简单工厂的 if 判断将会非常多,不容易维护。
- 违反了开闭原则
工厂模式
使用场景
- 客户端不需要知道它所创建的对象的类。只需要知道工厂名
- 客户端可以通过子类来指定创建对应的对象。
简单实现
工厂方法模式包含 4 个角色(要素):
- Product:抽象产品,定义工厂方法所创建的对象的接口,也就是实际需要使用的对象的接口
- ConcreteProduct:具体产品,具体的 Product 接口的实现对象
- Factory:工厂接口,也可以叫 Creator(创建器),申明工厂方法,通常返回一个 Product 类型的实例对象
- ConcreteFactory:工厂实现,或者叫 ConcreteCreator(创建器对象),覆盖 Factory 定义的工厂方法,返回具体的 Product 实例
UML图
kotlin
// 抽象产品,定义产品属性已经需要的方法
abstract class Pizza {
var name = ""
var dough = ""
var sauce = ""
var topping = mutableListOf<Any>()
fun prepare(){
println("Preparing ${name}")
println("Tossing dough")
println("Adding sauce...")
println("Adding toppings")
topping.forEach {
println(it)
}
}
fun bake(){
println("Bake for 25 minutes at 350")
}
open fun cut(){
println("Cutting the pizza into diagonal slices")
}
fun box() {
println("Place pizza in official PizzaStore box")
}
}
// 实际产品,实现抽象产品接口
class ChicagoStyleCheesePizza : Pizza() {
init {
name = "芝加哥起司披萨 sauce"
dough = "芝加哥起司披萨 sauce"
sauce = "芝加哥起司披萨 sauce"
topping.add("芝加哥起司披萨 topping")
}
override fun cut() {
println("芝加哥起司披萨切成4块")
}
}
// 实际产品,实现抽象产品接口
class NYStyleCheesePizza : Pizza() {
init {
name = "纽约起司披萨 sauce"
dough = "纽约起司披萨 sauce"
sauce = "纽约起司披萨 sauce"
topping.add("纽约起司披萨 topping")
}
}
// 抽象工厂,通常返回一个 Product 类型的实例对象
abstract class PizzaStore {
fun orderPizza(type : String) : Pizza{
var pizza : Pizza
pizza = createPizza(type)
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
return pizza
}
abstract fun createPizza(type : String): Pizza
}
// 实际工厂,覆盖 Factory 定义的工厂方法,返回具体的 Product 实例
class ChicagoPizzaStore : PizzaStore() {
override fun createPizza(type: String): Pizza {
return ChicagoStyleCheesePizza()
}
}
// 实际工厂,覆盖 Factory 定义的工厂方法,返回具体的 Product 实例
class NYPizzaStore : PizzaStore(){
override fun createPizza(type: String): Pizza {
return NYStyleCheesePizza()
}
}
工厂模式的优缺点
优点
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点
- 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,
抽象工厂模式
原型模式
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
使用场景
-
类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等。通过原型拷贝避免这些消耗。
-
通过new产生一个独享需要非常频繁的数据准备或访问权限。
-
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
实现方式
浅拷贝
kotlin
class Message : Cloneable{
var mName = ""
var mMoney = 0.0
init {
println("执行message构造函数")
}
fun setMessage(name : String, money : Double){
mName = name
mMoney = money
}
override fun clone(): Any {
return super.clone()
}
fun sendMessage(){
println("${mName} 您好:您今天消费了 ${mMoney} 元")
}
}
对于要克隆的对象,只会对对象中的基础数据类型属性进行克隆,而非基础数据类型属性只会复制一份引用给到克隆出来的对象,让克隆出来的对象的非基础数据类型属性指向原生对象的内部元素地址,这种拷贝叫做浅拷贝
深拷贝
kotlin
class Message : Cloneable{
var mName = ""
var mMoney = 0.0
var mList = mutableListOf<Any>()
init {
println("执行message构造函数")
}
fun setMessage(name : String, money : Double,list: String){
mName = name
mMoney = money
mList.add("这是钱")
}
public override fun clone(): Message {
val message = super.clone() as Message
message.mList = mList.toCollection(mutableListOf())
return message
}
fun sendMessage(){
println("${mName} 您好:您今天消费了 ${mMoney} 元 长度是 ${mList.size}")
}
}
相比于浅拷贝,深拷贝手动复制了列表等浅拷贝无法拷贝的数据
原型模式的优缺点
优点
-
性能提高。
-
逃避构造函数的约束。
缺点
-
配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
-
必须实现 Cloneable 接口。
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
使用场景
-
需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
-
隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
-
需要生成的对象内部属性本身相互依赖。
-
适合于一个具有较多的零件(属性)的产品(对象)的创建过程。
实现方式
kotlin
// 抽象建造者
abstract class Builder {
//汉堡
abstract fun bulidA(mes: String): Builder
//饮料
abstract fun bulidB(mes: String): Builder
//薯条
abstract fun bulidC(mes: String): Builder
//甜品
abstract fun bulidD(mes: String): Builder
//获取套餐
abstract fun build(): Product
}
// 产品
class Product {
var buildA = "汉堡"
var buildB = "饮料"
var buildC = "薯条"
var buildD = "甜品"
override fun toString(): String {
return "$buildA\n$buildB\n$buildC\n$buildD\n组成套餐"
}
}
// 具体建造者
class ConcreteBuilder : Builder() {
private val product: Product
override fun build(): Product {
return product
}
override fun bulidA(mes: String): Builder {
product.buildA = mes
return this
}
override fun bulidB(mes: String): Builder {
product.buildB = mes
return this
}
override fun bulidC(mes: String): Builder {
product.buildC = mes
return this
}
override fun bulidD(mes: String): Builder {
product.buildD = mes
return this
}
init {
product = Product()
}
}
fun main() {
val concreteBuilder = ConcreteBuilder()
val build = concreteBuilder
.bulidA("牛肉煲")
.bulidC("全家桶")
.bulidD("冰淇淋")
.build()
println(build.toString())
}
建造者模式的优缺点
优点
-
产品的建造和表示分离,实现了解耦。
-
将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
-
增加新的具体建造者无需修改原有类库的代码,易于拓展,符合"开闭原则"。
缺点
-
产品必须有共同点,限制了使用范围。
-
如内部变化复杂,会有很多的建造类,难以维护。