最近在看《设计模式与游戏完美开发》,文章将记录一些要点和一些设计模式实现
面向对象的六大原则
GoF定义的23种设计模式及应用场景
关于中介者模式,我们想象在一家互联网公司,有多个Colleague(同事),Colleague(同事)之间需要合作开发,不管是采用哪种版本控制软件,都可以抽象为一个Mediator(中介者)。而每个Colleague(同事)需要有版本控制的权限,即Mediator引用,而ConcreteMediator(具体中介者)则为ConcreteColleague(具体同事)提供服务。
Mediator.cs
public abstract class Mediator
{
public abstract void SendMessage(Colleague theColleage, string message);
}
ConcreateMediator.cs
public class ConcreateMediator : Mediator
{
ConcreateColleague1 m_Colleague1 = null;
ConcreateColleague2 m_Colleague2 = null;
public void SetColleage1(ConcreateColleague1 theColleague)
{
m_Colleague1 = theColleague;
}
public void SetColleage2(ConcreateColleague2 theColleague)
{
m_Colleague2 = theColleague;
}
public override void SendMessage(Colleague theColleague, string message)
{
if (theColleague == m_Colleague1)
{
m_Colleague2.Request(message);
} else if (theColleague == m_Colleague2)
{
m_Colleague1.Request(message);
}
}
}
Colleage.cs
public abstract class Colleague
{
protected Mediator m_Mediator = null;
public Colleague(Mediator mediator)
{
m_Mediator = mediator;
}
public abstract void Request(string message);
}
ConcreateColleage1.cs、ConcreateColleage2.cs
using UnityEngine;
public class ConcreateColleague1 : Colleague
{
public ConcreateColleague1(Mediator mediator) : base(mediator)
{
}
public void Action()
{
m_Mediator.SendMessage(this, "Concreate1执行");
}
public override void Request(string message)
{
Debug.Log("C2已接收:" + message);
}
}
using UnityEngine;
public class ConcreateColleague2 : Colleague
{
public ConcreateColleague2(Mediator mediator) : base(mediator)
{
}
public void Action()
{
m_Mediator.SendMessage(this, "Concreate2执行");
}
public override void Request(string message)
{
Debug.Log("C2已接收:" + message);
}
}
Test.cs
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
// 定义具体中介者、具体同事
ConcreateMediator pMediator = new ConcreateMediator();
ConcreateColleague1 pColleage1 = new ConcreateColleague1(pMediator);
ConcreateColleague2 pColleage2 = new ConcreateColleague2(pMediator);
// 添加同事
pMediator.SetColleage1(pColleage1);
pMediator.SetColleage2(pColleage2);
// 同事执行行为,中介者负责派发消息
pColleage1.Action();
pColleage2.Action();
}
void Update()
{
}
}
tips:有些情况下,Mediator会同时采用单例模式实现,在这种情况下,尽量避免从Mediator派生ConcreateMediator,这是因为当ConcreateMediator作为单例使用时,会因为单例只会存在唯一示例,当父类有多个派生类时会有“白马非马”的逻辑诡辩。同时为了遵循开闭原则,也要尽量依赖“接口”,而非依赖“实现”。
里氏替换原则有至少以下两种含义:
里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,那么共享的父类方法就应该保持不变,不能被子类重新定义。 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,应该将父类定义为抽象类。不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。
工厂模式是最简单的一种设计模式,由ConcreteCreate(具体工厂)产生ConcreateProduct(具体产品)
Creator.cs
public abstract class Creator
{
public abstract Product FactoryMethod();
}
ConcreateCreatorProductA.cs
using UnityEngine;
public class ConcreateCreatorProductA : Creator
{
public ConcreateCreatorProductA()
{
Debug.Log("产生工厂A");
}
public override Product FactoryMethod()
{
return new ConcreateProductA();
}
}
Product.cs
public abstract class Product { }
ConcreateProductA
using UnityEngine;
public class ConcreateProductA : Product
{
public ConcreateProductA()
{
Debug.Log("生产A");
}
}
tips:通过不同子类工厂产生不同品类的产品,当产品种类过多时,会导致“工厂子类暴增”。这时候可以配合FactoryMethod传参实现工厂模式
对于一个工厂来说,一条流水线应该可以生产多种产品吗,即生产流程与产品装配是可以解耦的。由Director(建造者指示者)对产品生产流程进行管控,而Builder(建造者)有很多ConcerteBuilder(具体建造者)则负责不同功能的装配。
Director.cs
public class Director
{
public Product m_Product;
public Director() { }
public void Construct(Builder theBuilder)
{
m_Product = new Product();
theBuilder.BuilderPart1(m_Product);
theBuilder.BuilderPart2(m_Product);
}
public Product GetResult()
{
return m_Product;
}
}
Builder.cs
public abstract class Builder
{
public abstract void BuilderPart1(Product theProduct);
public abstract void BuilderPart2(Product theProduct);
}
ConcreateBuilderA.cs
public class ConcreateBuilderA : Builder
{
public override void BuilderPart1(Product theProduct)
{
theProduct.AddPart("产品A工人:生产部件1");
}
public override void BuilderPart2(Product theProduct)
{
theProduct.AddPart("产品A工人:生产部件2");
}
}
using UnityEngine;
using System.Collections;
public class ConcreateBuilderB : Builder
{
public override void BuilderPart1(Product theProduct)
{
theProduct.AddPart("产品B工人:生产部件1");
}
public override void BuilderPart2(Product theProduct)
{
theProduct.AddPart("产品B工人:生产部件2");
}
}
Product.cs
using UnityEngine;
using System.Collections.Generic;
public class Product
{
private List<string> m_Part = new List<string>();
public Product() { }
public void AddPart(string v)
{
m_Part.Add(v);
}
public void ShowProduct()
{
foreach(var part in m_Part)
{
Debug.Log(part);
}
}
}
Test.cs
using UnityEngine;
public class Test : MonoBehaviour
{
Director director = new Director();
Product theProduct = null;
void Start()
{
director.Construct(new ConcreateBuilderA());
theProduct = director.GetResult();
theProduct.ShowProduct();
director.Construct(new ConcreateBuilderB());
theProduct = director.GetResult();
theProduct.ShowProduct();
}
}
“将对象以树状结构进行组合,用以表现部分与整体的层次关系”,UI的层次结构正好适合使用这种设计模式。
Component(组件界面)用来定义树形结构,以及每个结点都能使用的操作,Composite(组合结点,即根结点),实现Component定义的各个方法,Leaf(叶结点,终端结点)
IComponent.cs
using UnityEngine;
public abstract class IComponent
{
protected string m_Value;
public abstract void Operation();
public virtual void Add(IComponent theComponent)
{
Debug.Log("子类未实现");
}
public virtual void Remove(IComponent theComponent)
{
Debug.Log("子类未实现");
}
public virtual IComponent GetChild(int index)
{
Debug.Log("子类未实现");
return null;
}
}
Composite.cs
using UnityEngine;
using System.Collections.Generic;
public class Composite : IComponent
{
List<IComponent> m_Childs = new List<IComponent>();
public Composite(string value)
{
m_Value = value;
}
public override void Add(IComponent theComponent)
{
m_Childs.Add(theComponent);
}
public override void Remove(IComponent theComponent)
{
m_Childs.Remove(theComponent);
}
public override IComponent GetChild(int index)
{
return m_Childs[index];
}
public override void Operation()
{
Debug.Log("根节点执行" + m_Value);
foreach (IComponent theComponent in m_Childs)
{
theComponent.Operation();
}
}
}
Leaf.cs
using UnityEngine;
public class Leaf : IComponent
{
public Leaf(string value)
{
m_Value = value;
}
public override void Operation()
{
Debug.Log("叶节点执行:" + m_Value);
}
}
test.cs
using UnityEngine;
public class Test2 : MonoBehaviour
{
IComponent theRoot;
void Start()
{
theRoot = new Composite("Root");
theRoot.Add(new Leaf("Leaf1"));
theRoot.Add(new Leaf("Leaf2"));
IComponent theChild1 = new Composite("Child1");
theChild1.Add(new Leaf("child1.Leaf1"));
theChild1.Add(new Leaf("child1.Leaf2"));
theRoot.Add(theChild1);
IComponent theChild2 = new Composite("Child1");
theChild2.Add(new Leaf("child2.Leaf1"));
theChild2.Add(new Leaf("child2.Leaf2"));
theRoot.Add(theChild2);
theRoot.Operation();
}
}
tips:GameObject.Find()会遍历所有场景中的对象,而且在Unity中存在重名的问题,会产生性能问题。可以通过自己实现Find,保证只在指定的Canvas下查找
UI的功能在场景初始化时决定,保证UI逻辑与表现分离
责任链可以理解为数据结构的链表,每个Handler都有下一个Handler的引用。在关卡系统的设计上,很多项目会采用CreateStage(创建关卡)、CheckNextStage(检查进入下一关)方法来对场景进行操作,而采用责任链模式,则可以使用Handler(关卡对象)替代,通过类化关卡数据、通关条件能够提高关卡系统的灵活度。
Handler.cs
public abstract class Handler
{
protected Handler m_NextHandle = null;
public Handler(Handler theNextHandle)
{
m_NextHandle = theNextHandle;
}
public virtual void HandleRequest(int cost)
{
if (m_NextHandle != null)
{
m_NextHandle.HandleRequest(cost);
}
}
}
ConcreateHandle1.cs
using UnityEngine;
public class ConcreateHandle1 : Handler
{
public int m_CostCheck = 10;
public ConcreateHandle1(Handler theNextHandle) : base(theNextHandle) { }
public override void HandleRequest(int cost)
{
if (cost < m_CostCheck)
{
Debug.Log("ConcreateHandle2核准");
} else
{
base.HandleRequest(cost);
}
}
}
ConcreateHandle2.cs
using UnityEngine;
public class ConcreateHandle2 : Handler
{
public int m_CostCheck = 20;
public ConcreateHandle2(Handler theNextHandle) : base(theNextHandle) { }
public override void HandleRequest(int cost)
{
if (cost <= m_CostCheck)
{
Debug.Log("ConcreateHandle2核准");
}
else
{
base.HandleRequest(cost);
}
}
}
Test.cs
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour
{
void Start()
{
ConcreateHandle2 theHandle2 = new ConcreateHandle2(null);
ConcreateHandle1 theHandle1 = new ConcreateHandle1(theHandle2);
theHandle1.HandleRequest(5);
theHandle1.HandleRequest(20);
}
}
对于发布-订阅关系来说,一个Subject可以对应多个Observer,Subject维护观察者列表,并提供Notify()通知功能;Observer提供Update()方法,在Subject通知更新时调用。
Subject.cs
using System.Collections.Generic;
public abstract class Subject
{
List<Observer> m_Observer = new List<Observer>();
public void Attach(Observer theObserver)
{
m_Observer.Add(theObserver);
}
public void Detach(Observer theObserver)
{
m_Observer.Remove(theObserver);
}
public void Notify()
{
// 通知所有观察者
foreach (Observer observer in m_Observer)
{
observer.Update();
}
}
}
ConcreateSubject.cs
using UnityEngine;
using System.Collections;
public class ConcreateSubject : Subject
{
string m_SubjectState;
public void SetState(string state)
{
m_SubjectState = state;
Notify();
}
public string GetState()
{
return m_SubjectState;
}
}
Observer.cs
using UnityEngine;
using System.Collections;
public abstract class Observer
{
public abstract void Update();
}
ConcreateObserver.cs
using UnityEngine;
public class ConcreateObserver : Observer
{
string m_ObjectState;
ConcreateSubject m_Subject = null;
public ConcreateObserver(ConcreateSubject subject)
{
m_Subject = subject;
}
public override void Update()
{
Debug.Log("ConcreteObserver.Update");
Debug.Log("ConcreateObserver1:Subject" + m_ObjectState);
}
}
Test.cs
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour
{
ConcreateSubject theSubject = null;
ConcreateObserver theObserver = null;
// Use this for initialization
void Start()
{
theSubject = new ConcreateSubject();
theObserver = new ConcreateObserver(theSubject);
theSubject.Attach(theObserver1); // 观察者参与订阅
theSubject.SetState("Subject状态1"); // 主题修改状态,并通知
theObserver1.ShowState(); // 观察者接收通知并执行Update()操作
}
}
在不违反封装的原则下,获取一个对象的内部状态并保留在外部。当我们想办法去获取游戏系统的数据时,现有系统难免被改动,而如果由游戏系统主动提供数据,则可以保证系统的封装性,这也是备忘录模式的精髓。Originator(记录拥有者)会自动产出需要保存的记录Memento(记录保存者,我称之为记忆体),由Caretaker(记录看守者)维护多个Memento。
Originator.cs
using UnityEngine;
public class Originator
{
string m_State;
public void SetInfo(string state)
{
m_State = state;
}
public void ShowInfo()
{
Debug.Log("Originator State" + m_State);
}
public Memento CreateMemento()
{
Memento newMemento = new Memento();
newMemento.SetState(m_State);
return newMemento;
}
public void SetMemento(Memento m)
{
m_State = m.GetState();
}
}
Memento.cs
public class Memento
{
string m_State;
public string GetState()
{
return m_State;
}
public void SetState(string state)
{
m_State = state;
}
}
Caretaker.cs
using System.Collections.Generic;
public class Caretaker
{
Dictionary<string, Memento> m_Mementos = new Dictionary<string, Memento>();
public void AddMemento(string version, Memento theMemento)
{
if (m_Mementos.ContainsKey(version) == false)
m_Mementos.Add(version, theMemento);
else
m_Mementos[version] = theMemento;
}
public Memento GetMemento(string version)
{
if (m_Mementos.ContainsKey(version) == false)
return null;
return m_Mementos[version];
}
}
Test.cs
using System.Collections;
public class Test : MonoBehaviour
{
void Start()
{
Originator theOriginator = new Originator();
Caretaker theCaretaker = new Caretaker();
theOriginator.SetInfo("version1");
theOriginator.ShowInfo();
theCaretaker.AddMemento("1", theOriginator.CreateMemento());
theOriginator.SetInfo("version2");
theOriginator.ShowInfo();
theCaretaker.AddMemento("2", theOriginator.CreateMemento());
theOriginator.SetInfo("version3");
theOriginator.ShowInfo();
theOriginator.SetMemento(theCaretaker.GetMemento("2"));
theOriginator.ShowInfo();
theOriginator.SetMemento(theCaretaker.GetMemento("1"));
theOriginator.ShowInfo();
}
}