设计模式
一:设计模式之六大原则
六大原则是谁?
①单一职责原则 ②开放封闭原则 ③依赖倒置原则
④里式转换原则 ⑤接口隔离原则 ⑥迪米特原则
六大原则是我们提高面向对象编程代码质量的必备原则,另外还是我们理解设计模式的必备前提。
1.单一职责原则介绍:
单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。简单的说,就是一个类只负责一项职责(功能)。
简单点说:一个类只负责一件具体的事情,一个方法只完成一个特定的功能。当你发现一方法完成了两件事情的时候,就需要适当的重构成两个方法,类也是一样的。
在现实生活中有很多典型的例子体现出了单一职责:
比如:一个房子有客厅,有厨房,有卫生间,有卧室;每个房间都有自己“单一的职责/用途”。
2.开放封闭原则介绍:
开放封闭原则:软件实体(类,方法,模块)应该可以扩展,但是不可以修改。开放封闭原则简称为开闭原则。
比如说笔记本电脑:我们购买到的任何品牌的笔记本,都是“开放封闭”的。
封闭:整个笔记本是封闭的,且笔记本背部标明了“非专业人士,请勿试图拆卸
或者维修”以及“撕毁保修无效”的封条。
开放:指的是笔记本提供了若干个 USB 的插口,可供我们扩展。
简单分析:
笔记本本身具备键盘,触摸板,音响等功能,但是这些往往没有独立的外设好。
很多人会选择购买机械键盘,外置鼠标,外置音响,通过 USB 插口对笔记本原
有的功能进行扩展,这是一种正常的操作习惯,扩展而不是修改。
很少有人会在笔记本身上直接进行改动的,比如:把所有的键盘帽翘开,DIY
成机械键盘,拆开机器,更换一个更好的音响。如果你真这样做了,是有很大的
风险的,风险是你有可能破坏笔记本原有的结构,甚至出现大量的潜在风险。
3.依赖倒置原则介绍:
依赖倒置原则:针对抽象编程,不要针对实现编程;
高层模块不应该依赖于底层模块,两个模块都应该依赖于抽象(抽象类/接口)。
依赖倒置原则案例:
案例介绍:一个中国人和一个美国人上网,他们都需要搜索,聊天,看视频,但是他们上网的方式不同,比如美国人搜索用Google、聊天用MSN、看视频用Youtube,而中国人搜索用百度、聊天用QQ或者微信、看视频用腾讯或者爱奇艺,这个时候我们就可以引入依赖倒置原则
代码演示:
4.里氏转换原则介绍:
①一个软件实体如果使用的是一个父类的话,那么一定适用于其子类。而且它察
觉不出父类对象和子类对象的区别。
②在软件里面,把父类都替换成它的子类,软件的行为没有变化;简单点说,子
类型必须能够替换掉它们的父类型。
比如说:Unity 引擎是一个父类,Unity4.x,Unity5.x,Unity2017.x 都是这个父类下的子类。本身具备父类
的功能,同时又都有自己的新功能。
里氏转换原则名称的由来:
国外一位姓“里”的女士发表出来的数学理论,该理论最终以发表者的名字命名。
这种以发表者的名字进行命名的概念有很多,比如:牛顿定律,笛卡尔坐标系。
5.迪米特原则介绍:
迪米特原则:也叫做最少知识(知道)原则。
①如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。
如果其中一个类需要调用另外一个类的某一个方法的话,可以通过第三者转发这
个调用。
②一个对象应当对其他对象有尽可能少的了解。
③迪米特原则主要是强调了类与类之间的松耦合。
类与类之间的耦合度越低,越有利于代码的复用,一个处于低耦合的类被修改了,
不会对有关系的类造成影响。
比如说:很多公司的董事长都会有自己的助理(秘书),负责帮自己处理公司的零散事情。
比如说:公司需要开会,如果没有助理,那么董事长需要亲自去通知所有的员工;
但是如果有助理,只需要把开会这件事告诉助理,然后董事长就不需要管了,助
理会去通知所有的员工。
在董事长和公司员工之间,多出来了一个助理,就可以降低董事长和公司员工之
间的耦合度,这样可以提高董事长的时间价值和效率。
没有助理:董事长需要面对 N 个员工,1 对 N 的关系;
有了助理:董事长只需要面对助理 1 人,1 对 1 的关系;然后由助理去 1 对 N。
迪米特原则案例演示:
使用代码模拟董事长,员工,助理之间的关系。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// EmployeeManager(员工管理器).
/// </summary>
public class EmployeeManager : MonoBehaviour {
private Transform _Transform;
private GameObject prefab_Employee;
private List<GameObject> allEmployeeList = new List<GameObject>();
void Start()
{
_Transform = gameObject.GetComponent<Transform>();
prefab_Employee = Resources.Load<GameObject>("Employee");
CreateAllEmployee();
}
/// <summary>
/// 创建所有员工.
/// </summary>
private void CreateAllEmployee()
{
for(int i = 0; i < 10; i++)
{
GameObject go= GameObject.Instantiate<GameObject>(prefab_Employee,new Vector3(i*1.5f,0f,0f),Quaternion.identity,_Transform);
allEmployeeList.Add(go);
}
}
/// <summary>
/// 通知员工开会.
/// </summary>
public void SendMessageEmployeeMeeting(string time)
{
for(int i = 0; i < allEmployeeList.Count; i++)
{
allEmployeeList[i].GetComponent<Employee>().Meeting(time);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Employee(员工类)
/// </summary>
public class Employee : MonoBehaviour {
/// <summary>
/// 接收会议时间的方法.
/// </summary>
public void Meeting(string time)
{
Debug.Log("我知道了,今天下午"+time+"开会");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 助理(秘书)类
/// </summary>
public class Secretary : MonoBehaviour {
private EmployeeManager _EmployeeManager;//持有员工管理器脚本的引用.
void Start()
{
_EmployeeManager = GameObject.Find("EmployeeManager").GetComponent<EmployeeManager>();
}
/// <summary>
/// 通知助理召开会议.
/// </summary>
public void SendMessageSecretaryMeeting(string time)
{
_EmployeeManager.SendMessageEmployeeMeeting(time);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Boss(老板)类
/// </summary>
public class Boss : MonoBehaviour {
private Secretary _Secretary;//持有秘书脚本的引用.
void Start()
{
_Secretary = gameObject.GetComponent<Secretary>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
//通知开会.
_Secretary.SendMessageSecretaryMeeting("五点");
}
}
}
6.接口隔离原则介绍:
接口隔离原则:
①客户端不应该依赖它不需要的接口;
②一个类对另一个类的依赖应该建立在最小的接口上。
接口概念:
接口是一种能力,是一种规范,当我们对现在已经存在的类的继承关系进行功能
扩展的时候,就可以使用接口来完成相应的工作。
生活中的接口隔离原则:
公司内有很多部门,比如:开发部,业务部,财务部等等,每个部门内都有 N
个员工在工作。现在我把员工要做的事情,定义成一个接口,所有员工都实现这
个接口去做事情。
这个接口中定义的事情有:
工资计算,账务管理,客户扩展,客户维护,产品推销,程序开发,软件维护。
所有的员工都实现这个接口,去做事情。现在问题就出现了,不管是哪个部门的
员工,在实现了这个接口后,都有很多事情是不需要自己去做的。
当前这个接口就比较臃肿,我们需要对接口进行“功能隔离”:
财务部员工接口:
工资计算,账务管理;
业务部员工接口:
客户扩展,客户维护,产品推销;
开发部员工接口:
程序开发,软件维护;
这样对接口功能隔离,细分成不同部门的职责功能接口,不管是现有的员工,还
是以后新加入的员工,只需要实现自己部门对应的接口即可。
设计模式之创建型设计模式
一:简单工厂模式
简单工厂模式介绍:
①.模式介绍
简单工厂模式[Simple Factory],又叫做静态工厂模式。
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
②.模式代码结构:
抽象产品角色:简单工厂模式创建的所有对象的父类,用于描述类的公共部分;
具体产品角色:简单工厂模式创建出来的具体产品类;
简单工厂角色:简单工厂模式的核心,实现了创建产品的内部逻辑。
外部可以通过访问“简单工厂”就可以得到具体的产品,而不需要关注产品的具
体创建过程。
测试案例:
实现《90 坦克大战》游戏中的 NPC 坦克。【见图】
NPC 坦克分析:
①在该游戏中有多种不同类型的 NPC 坦克,我们可以用继承关系来对坦克的属
性和行为进行描述;
属性:移动速度,生命值; 行为:移动,射击;
②在游戏中,有一个 NPC 坦克的生成器,我们通过一个按键随机生成一个NPC 坦克。
坦克逻辑实现:
①定义一个TankBase的父类,在父类中实现共有的属性,用虚方法实现行为;
②定义三个坦克 子类,继承父类,重写父类中的虚方法;
③定义一个坦克工厂类,用于实例化和创建坦克 ;
④定义一个坦克管理器,用于坦克的逻辑。
代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 所有坦克的父类.
/// </summary>
public class TankBase : MonoBehaviour {
private int moveSpeed;
/// <summary>
/// 移动速度.
/// </summary>
public int MoveSpeed
{
get { return moveSpeed; }
set { moveSpeed = value; }
}
private int lifeValue;
/// <summary>
/// 生命值.
/// </summary>
public int LifeValue
{
get { return lifeValue; }
set { lifeValue = value; }
}
/// <summary>
/// 初始化坦克属性.
/// </summary>
public void InitTank(int moveSpeed,int lifeValue)
{
this.MoveSpeed = moveSpeed;
this.LifeValue = lifeValue;
}
/// <summary>
/// 坦克移动的虚方法.
/// </summary>
public virtual void TankMove()
{
Debug.Log("坦克移动");
}
/// <summary>
/// 坦克射击
/// </summary>
public virtual void TankShoot()
{
Debug.Log("开始射击");
}
public override string ToString()
{
return string.Format("移动速度:{0},生命值:{1}",this.moveSpeed,this.lifeValue);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankA :TankBase {
public override void TankShoot()
{
base.TankShoot();
Debug.Log("我是坦克A,一次发射一枚炮弹.");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankB : TankBase {
public override void TankShoot()
{
base.TankShoot();
Debug.Log("我是坦克B,一次发射二枚炮弹.");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankC :TankBase {
public override void TankShoot()
{
base.TankShoot();
Debug.Log("我是坦克C,一次发射三枚炮弹.");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 坦克工厂类.
/// </summary>
public class TankFctory : MonoBehaviour {
private GameObject prefab_TankA;
private GameObject prefab_TankB;
private GameObject prefab_TankC;
void Awake()
{
prefab_TankA = Resources.Load<GameObject>("TankA");
prefab_TankB = Resources.Load<GameObject>("TankB");
prefab_TankC = Resources.Load<GameObject>("TankC");
}
/// <summary>
/// 创建坦克.
/// </summary>
public TankBase CreateTank(string tankName)
{
TankBase tankBase = null;
switch (tankName)
{
case "TankA":
tankBase = GameObject.Instantiate<GameObject>(prefab_TankA).GetComponent<TankBase>();
tankBase.InitTank(2, 100);
break;
case "TankB":
tankBase = GameObject.Instantiate<GameObject>(prefab_TankB).GetComponent<TankBase>();
tankBase.InitTank(4, 200);
break;
case "TankC":
tankBase = GameObject.Instantiate<GameObject>(prefab_TankC).GetComponent<TankBase>();
tankBase.InitTank(6, 300);
break;
}
return tankBase;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 所有坦克管理器.
/// </summary>
public class TankManager : MonoBehaviour {
private List<string> tankList;
private TankFctory _TankFctory;
void Start()
{
_TankFctory = gameObject.GetComponent<TankFctory>();
tankList = new List<string>();
tankList.Add("TankA");
tankList.Add("TankB");
tankList.Add("TankC");
}
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
int index = Random.Range(0, tankList.Count);
TankBase tankBase = _TankFctory.CreateTank(tankList[index]);
tankBase.TankMove();
tankBase.TankShoot();
Debug.Log(tankBase.ToString());
}
}
}
什么是 UML 类图?
UML 类图指的是用图形把类,接口,属性,方法,继承,实现等等面向对象中
的特点和关系,用图形的方式展现出来。
简单工厂模式 UML 简图 [见图]
继承关系:UML 表示为“实线+空心三角”;实线的尾指向的子类,空心三角
指向的是父类。
依赖关系:UML 表示为“虚线+箭头”;虚线的尾指向的是依赖者,箭头指向
的是被依赖者。
二:工厂方法模式
简单工厂模式的缺点
简单工厂模式不符合“开发封闭原则”。
当我们需要增加新的产品的时候,需要对坦克工厂类中增加新的 case 语句块,
现在的坦克工厂类不够“封闭”,且产品量很多的时候,工厂类中的 case 语句
块也会很多。
工厂方法模式介绍:
①.模式介绍
工厂方法模式[Factory Method],该模式定义了一个用于创建对象的接口,让
子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式是对简单工厂模式的进一步抽象和升级,但绝不是替代。
②.模式代码结构
抽象产品角色:创建的所有对象的父类,用于描述类的公共部分;
具体产品角色:创建出来的具体产品类;
抽象工厂角色:工厂的抽象父类,定义生成产品的方法;
具体工厂角色:抽象工厂角色的具体子类,每一个具体产品都对应一个工厂。
工厂方法模式演示:
①定义一个坦克工厂接口,接口内实现一个生成坦克的方法;
②为每一个坦克子类都创建一个针对性的工厂类,该工厂类需要实现坦克接口;
③客户端脚本使用工厂方法模式。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 坦克工厂接口.
/// </summary>
interface ITankFactory {
TankBase CreateTank(GameObject go);
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 坦克A工厂类.
/// </summary>
public class TankAFactory : ITankFactory
{
public TankBase CreateTank(GameObject go)
{
TankBase tankBase= GameObject.Instantiate<GameObject>(go).GetComponent<TankBase>();
tankBase.InitTank(2,100);
return tankBase;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 坦克B工厂类.
/// </summary>
public class TankBFactory : ITankFactory
{
public TankBase CreateTank(GameObject go)
{
TankBase tankBase = GameObject.Instantiate<GameObject>(go).GetComponent<TankBase>();
tankBase.InitTank(4, 200);
return tankBase;
}
}