什么是耦合

耦合性(英语:Coupling,dependency,或称耦合力或耦合度)是一种软件度量,是指一程序中,模块及模块之间信息或参数依赖的程度。

内聚性是一个和耦合性相对的概念,一般而言低耦合性代表高内聚性,反之亦然。耦合性和内聚性都是由提出结构化设计概念的赖瑞·康斯坦丁所提出。低耦合性是结构良好程序的特性,低耦合性程序的可读性及可维护性会比较好。

耦合的分类

耦合性可以是低耦合性(或称为松散耦合),也可以是高耦合性(或称为紧密耦合)。以下列出一些耦合性的分类,从高到低依序排列:

内容耦合

也称为病态耦合(pathological coupling)当一个模块直接使用另一个模块的内部数据,或通过非正常入口而转入另一个模块内部。

共享耦合/公共耦合

也称为全局耦合(global coupling.)指通过一个公共数据环境相互作用的那些模块间的耦合。公共耦合的复杂程序随耦合模块的个数增加而增加。

外部耦合

发生在二个模块共享一个外加的数据格式、通信协议或是设备界面,基本上和模块和外部工具及设备的沟通有关。

控制耦合

指一个模块调用另一个模块时,传递的是控制变量(如开关、标志等),被调模块通过该控制变量的值有选择地执行块内某一功能。

特征耦合/标记耦合

也称为数据结构耦合,是指几个模块共享一个复杂的数据结构,如高级语言中的数组名、记录名、文件名等这些名字即标记,其实传递的是这个数据结构的地址。

数据耦合/数据耦合

是指模块借由传入值共享数据,每一个数据都是最基本的数据,而且只分享这些数据(例如传递一个整数给计算平方根的函数)。

消息耦合

可以借由以下二个方式达成:状态的去中心化(例如在对象中),组件间利用传入值或消息传递 (计算机科学)来通信。

单一职责 SRP

就一个类而言,应该仅有一个引起它变化的原因

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到想不到的破坏。

举例

电脑配件

开放封闭原则

软件实体(类、模块、函数等等)应该可以扩展,但是不可修改

  • 对扩展是开放的
  • 对更改是封闭的

我们在最初编写代码时,假设变化不会发生。当变化发生时,就创建抽象来隔离以后发生的同类变化,即面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码

举例

策略模式中如何扩展武器

迪米特法则 LOD

如果两个类不必彼此直接通信,那么这两个雷就不应当发生直接的互相作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用

  • 在类的结构设计上,每一个类都应当尽量降低类成员的访问权限
  • 其根本思想是,强调了类之间的松耦合
  • 类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及

举例

Manager

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点

实现

Unity是单线程的,但是在某些特殊的情况下,如果不lock,会出现两个instance,下面是通用线程安全的实现方式

Singleton.cs

public abstract class Singleton<T> : Module where T : Singleton<T>, new() {
    private static T      _instance = default(T);
    private static object _lock      = new object();

    public static T Instance {
        get
        {
            if(_instance == null) {
                lock(_lock) {
                    if(_instance == null) _instance = new T();
                }
            }

            return _instance;
        }
    }
}

工厂模式

在调用API时,调用者不关心对象如何实例化,只关心要实例化哪一个对象

在Main方法中对工厂类进行调用,所有Enemy的实例化全放在Factory中进行,上层逻辑只关心Enemy.Attack()方法,并不关心这个Enemy有什么其他的属性

实现

using System;
using UnityEngine;
using System.Reflection;

public class Enemy {
    public virtual void Attack() { }
}

public class MonsterA : Enemy {
    public override void Attack() {
        base.Attack();
        Debug.Log("MonsterA Attack!");
    }
}

public class MonsterB : Enemy {
    public override void Attack() {
        base.Attack();
        Debug.Log("MonsterB Attack!");
    }
}

public enum EnemyType {
    MonsterA,
    MonsterB,
}

public class EnemyFactory{
    public Enemy SpawnEnemyByType(EnemyType type) {
        switch(type) {
            case EnemyType.MonsterA: return new MonsterA();
            case EnemyType.MonsterB: return new MonsterB();
            default:                 throw new ArgumentOutOfRangeException(nameof(type), type, null);
        }
    }
}

public class FactoryUseageExample {
    public void Main() {
        EnemyFactory enemyFactory = new EnemyFactory();
        Enemy A = enemyFactory.SpawnEnemyByType(EnemyType.MonsterA);
        A.Attack();
    }
}

观察者模式

定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己

需求

  • 当天黑了,怪物A睡觉
  • 当天黑了,怪物B起床

实现

TimeController.cs

namespace Observer {
    public class TimeController {
        public Action Sunset;
        public void OnSunset() { Sunset?.Invoke(); }
    }
}

MonsterA.cs

namespace Observer {
    public class MonsterA {
        public void OnSunset() { Debug.Log("Sleep"); }
    }
}

MonsterB.cs

namespace Observer {
    public class MonsterB {
        public void OnSunset() { Debug.Log("WakeUp"); }
    }
}

具体调用如下:

namespace Observer {
    public class Main : MonoBehaviour {
        private void Start() {
            TimeController tc = new TimeController();
            MonsterA a = new MonsterA();
            MonsterB b = new MonsterB();

            tc.Sunset += a.OnSunset;
            tc.Sunset += b.OnSunset;

            // 日落
            tc.OnSunset();
        }
    }
}

代理模式

为其他对象提供一种代理以控制对这个对象的访问

需求

  • NPC送给玩家一朵花

实现

IGiveGift.cs

namespace Proxy {
    public interface IGiveGift {
        void GiveFlower();
    }
}

Giver.cs

 namespace Proxy {
     public class Giver : IGiveGift {
         private Player _player;

         public Giver(Player player) { _player = player; }

         public void GiveFlower() { Debug.Log("Give flower to " + _player.Name); }
     }
 }

Player.cs

namespace Proxy {
    public class Player {
        public  string Name { get; set; }
    }
}

具体调用如下:

namespace Proxy {
    public class Main : MonoBehaviour {
        private void Start() {
            var player = new Player {Name = "Player One"};

            var proxy = new Proxy(player);

            proxy.GiveFlower();
        }
    }
}

装饰模式

动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活

需求

  • 为游戏角色开发服饰穿搭系统

实现

Person.cs

namespace Decorator {
    public class Person {
        private string _name;

        public Person() { }
        public Person(string name) { _name = name; }

        public virtual void Show() { Debug.Log($"Dressed on {_name}"); }
    }
}

ConcreteDecorator.cs

// 具体装饰类
namespace Decorator {
    public class TShirts : Finery {
        public override void Show() {
            Debug.Log("T-Shirt");
            base.Show();
        }
    }

    public class Skirt : Finery {
        public override void Show() {
            Debug.Log("Skirt");
            base.Show();
        }
    }

    public class Shoe : Finery {
        public override void Show() {
            Debug.Log("Shoe");
            base.Show();
        }
    }
}

Finery.cs

namespace Decorator {
    /// <summary>
    /// 服饰类(decorator)
    /// </summary>
    public class Finery : Person {
        protected Person _component;

        public void Decorate(Person component) { _component = component; }

        public override void Show() { _component?.Show(); }
    }
}

具体调用如下:

namespace Decorator {
    public class Main : MonoBehaviour {
        private void Start() {

            Person player = new Person("Player");
            TShirts ts = new TShirts();
            Skirt sk = new Skirt();
            Shoe sh = new Shoe();

            ts.Decorate(player);
            sk.Decorate(ts);
            sh.Decorate(sk);

            sh.Show();
        }
    }
}

外观模式

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

何时使用

  • 在设计初期阶段,应该有意识的将不同的两个层隔离
  • 在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观Facade可以提供一个简单的接口,减少他们之间的依赖
  • 在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作

举例实现

SubSystem.cs

namespace Facade {
    public class SubSystemOne {
        public void FunctionOne() { Debug.Log("SubSystem one func"); }
    }

    public class SubSystemTwo {
        public void FunctionTwo() { Debug.Log("SubSystem two func"); }
    }

    public class SubSystemThree {
        public void FunctionThree() { Debug.Log("SubSystem three func"); }
    }
}

SubFacade.cs

namespace Facade {
    public class SystemFacade {
        private SubSystemOne   _one;
        private SubSystemTwo   _two;
        private SubSystemThree _three;

        public SystemFacade() {
            _one   = new SubSystemOne();
            _two   = new SubSystemTwo();
            _three = new SubSystemThree();
        }

        public void FunctionA() {
            Debug.Log("Funcs A");
            _one.FunctionOne();
            _two.FunctionTwo();
        }

        public void FunctionB() {
            Debug.Log("Funcs B");
            _two.FunctionTwo();
            _three.FunctionThree();
        }
    }
}

具体调用:

namespace Facade {
    public class Main : MonoBehaviour {
        private void Start() {
            SystemFacade facade = new SystemFacade();

            facade.FunctionA();
            facade.FunctionB();
        }
    }
}

策略模式

在调用API时,调用者不关心处理过程,传入参数后,只关心处理结果

需求

  • 角色可像怪物实施攻击,一次攻击后,怪物损失部分HP,HP <= 0 怪物死亡
  • 角色可以装备不同的武器,如:木剑、铁剑、魔剑
  • 木剑攻击力20,铁剑攻击力50,魔剑攻击力100且50%几率出现暴击

实现

对于这种策划案可以将不同武器攻击看成一种策略

IAttackStrategy.cs

namespace Strategy {
    public interface IAttackStrategy {
        void AttackTarget(Monster monster);
    }
}

WoodSword.cs

namespace Strategy {
    public class WoodSword : IAttackStrategy {
        public void AttackTarget(Monster monster) {
            monster.Inform(20);
        }
    }
}

IronSword.cs

namespace Strategy {
    public class IronSword : IAttackStrategy {
        public void AttackTarget(Monster monster) {
            monster.Inform(50);
        }
    }
}

MagicSword.cs

namespace Strategy {
    public class MagicSword : IAttackStrategy {

        private  Random m_Random = new Random();

        public void AttackTarget(Monster monster) {
            var loss = m_Random.NextDouble() < 0.5f ? 100 : 200;
            if(loss == 200) {
                Debug.Log("Critical strike!");
            }
            monster.Inform(loss);
        }
    }
}

Monster.cs

namespace Strategy {
    public class Monster {
        /// <summary>
        /// 怪物名字
        /// </summary>
        public  string Name { get; set; }
        /// <summary>
        /// 怪物血量
        /// </summary>
        private int    HP   { get; set; }

        public Monster(string name, int hp) {
            this.Name = name;
            this.HP   = hp;
        }
        /// <summary>
        /// 怪物被攻击时,调用的方法,用来处理被攻击后的状态更改
        /// </summary>
        /// <param name="loss"></param>
        public void Inform(int loss) {
            if(HP <= 0) {
                Debug.Log("Monster already dead");
                return;
            }

            HP -= loss;

            if(HP <= 0) {
                Debug.Log("Monster : " + Name + " is dead");
            } else {
                Debug.Log("Monster : " + Name + ", lost : " + loss + "HP");
            }
        }
    }
}

Role.cs

namespace Strategy {
    public class Role {
        public IAttackStrategy Weapon { get; set; }

        public void Attack(Monster monster) { Weapon.AttackTarget(monster); }
    }
}

具体调用如下:


namespace Strategy { public class Main : MonoBehaviour { private void Start() { Monster monster1 = new Monster("Monster A ", 50); Monster monster2 = new Monster("Monster B ", 70); Monster monster3 = new Monster("Monster C ", 90); // 木剑攻击 Role role = new Role {Weapon = new WoodSword()}; role.Attack(monster1); // 铁剑攻击 role.Weapon = new IronSword(); role.Attack(monster2); // 魔剑攻击 role.Weapon = new MagicSword(); role.Attack(monster3); } } }

状态模式

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类中,将复杂的判断逻辑简化

需求

  • 12点前,打造武器
  • 12点~13点午睡
  • 13点~17点,打造武器
  • 如果订单比较多则加班
  • 加班可以加到21点
  • 21点之后睡觉

实现

State.cs

namespace StatePattern {
    public abstract class State {
        public abstract void DoNext(Work work);
    }
}

AfternoonState.cs

namespace StatePattern {
    public class AfternoonState : State {
        public override void DoNext(Work work) {
            if(work.Hour < 17) {
                Debug.Log($"Now is {work.Hour}, making weapon. ");
            } else {
                work.SetState(new EveningState());
                work.DoNext();
            }
        }
    }
}

EveningState.cs

namespace StatePattern {
    public class EveningState : State {
        public override void DoNext(Work work) {
            if(work.TaskFinished) {
                work.SetState(new ResetState());
                work.DoNext();
            } else {
                if(work.Hour < 21) {
                    Debug.Log($"Now is {work.Hour}, tired");
                } else {
                    work.SetState(new SleepingState());
                    work.DoNext();
                }
            }
        }
    }
}

ForenoonState.cs

namespace StatePattern {
    public class ForenoonState : State {
        public override void DoNext(Work work) {
            if(work.Hour < 12) {
                Debug.Log($"Now is {work.Hour}, time to work");
            } else {
                work.SetState(new NoonState());
                work.DoNext();
            }
        }
    }
}

NoonState.cs

namespace StatePattern {
    public class NoonState : State{
        public override void DoNext(Work work) {
            if(work.Hour < 13) {
                Debug.Log($"Now is {work.Hour}, time to sleep. ");
            } else {
                work.SetState(new AfternoonState());
                work.DoNext();
            }
        }
    }
}

ResetState.cs

namespace StatePattern {
    public class ResetState : State {
        public override void DoNext(Work work) {
            Debug.Log($"Now is {work.Hour}, time to go home.");
        }
    }
}

SleepingState.cs

namespace StatePattern {
    public class SleepingState : State {
        public override void DoNext(Work work) {
            Debug.Log($"Now is {work.Hour}, time to sleep.");
        }
    }
}

Work.cs

namespace StatePattern {
    public class Work {

        public int Hour {
            get => _hour;
            set
            {
                _hour = value;
                DoNext();
            }
        }

        public bool TaskFinished {
            get => _finished;
            set => _finished = value;
        }

        private State _current;
        private bool  _finished;
        private int _hour;

        public Work() {
            _current = new ForenoonState();
        }

        public void SetState(State state) { _current = state; }

        public void DoNext() { _current.DoNext(this); }
    }
}

具体调用如下:

namespace StatePattern {
    public class Main : MonoBehaviour {
        private void Start() {
            Work work = new Work();

            work.Hour = 9;
            work.Hour = 10;
            work.Hour = 12;
            work.Hour = 13;

            work.Hour = 14;
            work.Hour = 17;

            work.TaskFinished = false;
            work.Hour = 19;
            work.Hour = 22;
        }
    }
}

组合模式

将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性

  • 需求中是体现部分与整体层次的结构
  • 用户可以忽略组合对象和单个对象的不同,统一地使用组合结构中的所有对象

需求

  • 游戏公会有总公会和分工会
  • 分工会也可以有自己的分工会
  • 各个分工会有自己不同的职责

实现

Society.cs

namespace Composite {
    public abstract class Society {
        public string Name;

        public Society(string name) { Name = name; }

        public abstract void Add(Society    society);
        public abstract void Remove(Society society);
        public abstract void Display(int    depth);
        public abstract void LineOfDuty();
    }
}

ConcreteSociety.cs

namespace Composite {
    public class ConcreteSociety : Society {
        private List<Society> _childrens = new List<Society>();

        public ConcreteSociety(string name) : base(name) { }

        public override void Add(Society society) { _childrens.Add(society); }

        public override void Remove(Society society) { _childrens.Remove(society); }

        public override void Display(int depth) {
            Debug.Log(new String('-', depth) + Name);
            foreach(var children in _childrens) {
                children.Display(depth + 4);
            }
        }

        public override void LineOfDuty() {
            foreach(var children in _childrens) {
                children.LineOfDuty();
            }
        }
    }
}

BranchSociety.cs

namespace Composite {
    public class EastSociety : Society {
        public EastSociety(string name) : base(name) { }

        public override void Add(Society society) { }

        public override void Remove(Society society) { }

        public override void Display(int depth) { Debug.Log(new string('-', depth) + Name); }

        public override void LineOfDuty() { Debug.Log($"{Name} duty is east."); }
    }

    public class WestSociety : Society {
        public WestSociety(string name) : base(name) { }

        public override void Add(Society society) { }

        public override void Remove(Society society) { }

        public override void Display(int depth) { Debug.Log(new string('-', depth) + Name); }

        public override void LineOfDuty() { Debug.Log($"{Name} duty is west."); }
    }

    public class SouthSociety : Society {
        public SouthSociety(string name) : base(name) { }

        public override void Add(Society society) { }

        public override void Remove(Society society) { }

        public override void Display(int depth) { Debug.Log(new string('-', depth) + Name); }

        public override void LineOfDuty() { Debug.Log($"{Name} duty is south."); }
    }
}

具体调用如下:

namespace Composite {
    public class Main : MonoBehaviour {
        private void Start() {
            ConcreteSociety root = new ConcreteSociety("root");

            ConcreteSociety west = new ConcreteSociety("west_root");
            ConcreteSociety east = new ConcreteSociety("east_root");
            ConcreteSociety south = new ConcreteSociety("south_root");

            west.Add(new WestSociety("west_1"));
            west.Add(new WestSociety("west_2"));
            west.Add(new WestSociety("west_3"));

            east.Add(new EastSociety("east_1"));
            east.Add(new EastSociety("east_2"));
            east.Add(new EastSociety("east_3"));

            south.Add(new WestSociety("south_1"));
            south.Add(new WestSociety("south_2"));
            south.Add(new WestSociety("south_3"));

            root.Add(east);
            root.Add(west);
            root.Add(south);

            root.Display(1);
            root.LineOfDuty();
        }
    }
}

What doesn’t kill you makes you stronger.