C#设计模式:六大原则(下)

四、 接口隔离原则(Interface Segregation Principe,ISP)

类的依赖关系应建立在最小接口上,不要都塞在一起。即客户端不应该依赖它不需要的接口。

  根据上面的定义可以看出,对接口的建立要最小化,而不是依赖所有功能都塞在一起的大而全的接口。换种说法就是,方法尽量要细化,要少。当然,也不要拆分成一个一个的,而是要把一些功能紧密绑的方法封装起来,不要暴露太多细节。哇,这是不单一职责吗?不对,它们的审视角度是不同的,一个是接口的依赖,要求接口的方法尽量要少;一个是职责的分离,类和接口的职责要单一。接口隔离原则就是要求只提供尽可能小的接口,需要高内聚,不需要的行为要隐藏起来。当然,拆分接口时,也需要满足单一职责原则。
  来看一个手机的例子吧,手机在例子的世界还是很火爆的,现在是至少人手一部了。看图4.1

图4.1 使用手机的类图

  定义了一个手机的接口,手机可以打电话,发短信,上网,玩游戏等,然后使用了一个场景类People来使用手机。看起来是不是很完美,但仔细想想,ICellphone 这个接口有没有最优设计,是不是功能有点太多了,当然,单从单一职责上考滤是没有问题的,加上接口隔离原则的话,就不同了。时间回到手机刚诞生的年代,那时候的手机是不是只能打个电话,发个短信。手机在迭代的过程中,才出现了各种功能,上网,玩游戏,在线支付等,当今平板的出现,除了上网等高级功能外,基础的打电话,发短信功能反而没有了。这样功能都封装在一起,是不是就过度了,而且都暴露到了使用场景中,不就有问题了吗。那我们根据接口隔离原则重新修改一下类图,如图4.2所示

图4.2 修改后的使用手机的类图

  这样,不管以后使用什么手机,都可保持接口的稳定。需要什么,就继承什么,如果是平板,就不需要电话和短信,保持了接口的最小化。来看一下代码实现:

/// <summary>
/// 基础手机功能
/// 打电话、发短信
/// </summary>
public interface IBaseCellphone
{
    void Call();
    void Text();
}

/// <summary>
/// 上网玩游戏的功能
/// </summary>
public interface IOnlineGameCellphone
{
    void Online();
    void PlayGame();
}

/// <summary>
/// 现代手机,即可打电话,短信,还可以上网玩游戏
/// </summary>
public class Cellphone : IBaseCellphone, IOnlineGameCellphone
{
    public void Call()
    {
        Console.WriteLine("打电话");
    }

    public void Text()
    {
        Console.WriteLine("发短信");
    }

    public void Online()
    {
        Console.WriteLine("手机已连网");
    }

    public void PlayGame()
    {
        Console.WriteLine("开始玩游戏");
    }
}

场景中使用

/// <summary>
/// 现实生活中的场景,使用手机
/// </summary>
public class People
{
    public int Id { get; set; }
    public string Name { get; set; }

    /// <summary>
    /// 一些人只使用手机的基本功能
    /// </summary>
    /// <param name="phone"></param>
    public void UsePhone(IBaseCellphone cellphone)
    {
        Console.WriteLine("我是 {0},我只用基础的功能", this.Name);
        cellphone.Call();
        cellphone.Text();
    }

    /// <summary>
    /// 只想上网玩游戏
    /// </summary>
    /// <param name="cellphone"></param>
    public void PlayOnlineGame(IOnlineGameCellphone cellphone)
    {
        Console.WriteLine("我是 {0},我只想上网玩游戏", this.Name);
        cellphone.Online();
        cellphone.PlayGame();
    }
}

五、迪米特法则(Law of Demeter,LOD)

一个对象应尽可能少的了解其它对象

  迪米特法则也称最少知识原则(Least Knowledge Principle,LKP),名字虽不同,规则确是同样的。说的都是类与类之间的关系,一个类在使用其它类时,不需要知道其细节,知道的越少越好,你内部如何复杂多变,都是你自己的事情,我不关心,我只关心调用方法,取得结果,其它的一概不关心。迪米特法则的指导思想就是使类与类之间保持松耦合的关系。主要包括如下:

  1. 不跟非直接的朋友说话
  2. 只是内部服务的属性,要封闭到内部
  3. C#中[Serializable]特性,尽量少用,牵涉到序列化的问题

还是用实例来说话吧,有一个关于学校的日常故事,老师让班长多关注下同学们的学习情况,有没有好好听课,认真写作业。让我们来用程序实现一下,先看类图,如图5.1所示:


图5.1 老师让班长检查学生学习情况

  分别定义了老师,班长,学生三个角色,Teacher通过Command方法让Monitor检查学生的学习情况,Monitor通过CheckLearn方法检查学生,来看下代码实现:

/// <summary>
/// 学生
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
}

/// <summary>
/// 班长
/// </summary>
public class Monitor
{
    public void CheckLearn(List<Student> students)
    {
        students.ForEach(item =>
        {
            //检查有没有好好学习
            var learn = new Random(Guid.NewGuid().GetHashCode()).Next(0, 2) == 1 ? "有" : "没有";
            Console.WriteLine($"{item.Name},{learn}好好学习");
        });
    }
}

/// <summary>
/// 老师
/// </summary>
public class Teacher
{
    public void Command(Monitor monitor)
    {
        //初始化学生
        var list = new List<Student>
        {
            new Student {Id = 1, Name = "张三"},
            new Student {Id = 2, Name = "李四"},
            new Student {Id = 3, Name = "王五"},
            new Student {Id = 4, Name = "赵六"},
            new Student {Id = 5, Name = "陈七"}
        };

        //班长检查学习情况
        monitor.CheckLearn(list);
    }
}

场景中调用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var teacher = new Teacher();
            teacher.Command(new Monitor());
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

运行结果:
张三,没有好好学习
李四,有好好学习
王五,有好好学习
赵六,有好好学习
陈七,没有好好学习

   整个过程都实现了,貌似没有什么问题,但回过头来想想,老师有几个直接的朋友类,这里面其实就只有一个,那就是班长,直接告诉的班长。那老师也依赖了学生啊,不也是朋友类吗?迪米特法则告诉我们,像这种方法体内的类不属于朋友类,一个类只和朋友说话,班长才直接和学生有交流。Command中出现List<Student>列表,会影响程序的稳定性。同时,我们看班长检查学习情况,只需要起检查作用,学生学习是自己的事情,比如他要认真听课,好好写作业。有没有好好学习也要有个标准,不能主观来判断,上面实例中处理的也比较简单,我们来深入下,当然,也不是很深入,再加上上面朋友类的问题,来重构一下程序,修改一下类图,如图5.2所示:


图5.2 修改后的检查学习情况类图

断开了非直接的朋友,学生的学习情况也内聚到自身,具体看一下代码实现:

/// <summary>
/// 学生
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    private readonly Random _random = new Random(Guid.NewGuid().GetHashCode());

    private int _score;  //只是内部服务的属性,要封闭到内部
    private const int GoodScore = 180;

    public void Learn()
    {
        this.Lesson();
        this.Homework();
        var learnString = this._score > GoodScore ? "有" : "没有";
        Console.WriteLine($"{this.Name},{learnString}好好学习");
    }

    /// <summary>
    /// 听课
    /// 内部方法,尽量减少公开的方法和属性
    /// </summary>
    private void Lesson()
    {
        this._score += _random.Next(150);
    }

    /// <summary>
    /// 写作业
    /// </summary>
    private void Homework()
    {
        this._score += _random.Next(150);
    }
}

/// <summary>
/// 班长
/// </summary>
public class Monitor
{
    public List<Student> StudentList { get; set; }

    public void CheckLearn()
    {
        StudentList.ForEach(item =>
        {
            //只关心调用方法,不需要知道细节
            item.Learn();
        });
    }
}

/// <summary>
/// 老师
/// </summary>
public class Teacher
{
    public void Command(Monitor monitor)
    {
        //断开了对Student的依赖,减少依赖,不跟非直接的朋友说话
        //班长检查学习情况
        monitor.CheckLearn();
    }
}

场景中调用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            //初始化学生
            var studentList = new List<Student>
            {
                new Student {Id = 1, Name = "张三"},
                new Student {Id = 2, Name = "李四"},
                new Student {Id = 3, Name = "王五"},
                new Student {Id = 4, Name = "赵六"},
                new Student {Id = 5, Name = "陈七"}
            };

            var teacher = new Teacher();
            teacher.Command(new Monitor {StudentList = studentList});
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

六、开闭原则(Open Closed Principle,OCP)

一个软件实体应当对扩展开放,对修改封闭

   一个软件或系统在开发的过程中,以及上线生产后,随着时间的推移,都会产生变化,这是铁定的事实。那我们在设计时就要尽量适应这些变化,来提高系统的稳定性和灵活性,开闭原则就是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。开闭原则指导我们如何建立一个稳定的、灵活的系统
   开闭原则是最基础的原则,也是最重要的面向对象设计原则,在面向对象的开发时,都会提到的原则。开闭原则就是一个目标,其它五大原则都是实现手段,它就像一个口号,你们都要奔着这个口号来。要满足开闭原则,就需要对系统进行抽象化设计,抽象化是开闭原则的关键,换句说法就是对系统进行抽象约束。
   每个人心中,都有一个武侠梦,梦想到有朝一日,能成为一个侠客。如今,各种武侠类游戏也层出不穷,好多人都喜欢玩,毕竟有个圆梦的地方了。游戏中,有各种门派,如少林,武当,玩家选择各自喜欢的门派,玩的美滋滋的。我们来实现下这个过程,类图如6.1所示

图6.1 玩武侠游戏的类图

   上图中,Player可以玩游戏了,武当、少林都能玩,可是游戏是会迭代的,有一天上了一个新版本,加入了一个新的门派,不但要修改Player类,还要更改场景中的处理,违反了开闭原则。重构一下,如类图6.2所示

图6.2 重构后的玩武侠游戏的类图

   增加了一个门派接口,各门派需要实现接口。Player只依赖接口,通过场景类来确定玩家玩什么门派,来看下代码实现:

public interface IFaction
{
    void Characteristic();
}

public class WuDang : IFaction
{
    public void Characteristic()
    {
        Console.WriteLine("武当派是攻击系的,内功远程群攻,很6。");
    }
}

public class ShaoLin : IFaction
{
    public void Characteristic()
    {
        Console.WriteLine("少林派是防御系的,内功防御,你们都打不疼我。");
    }
}

public class Player
{
    public void PlayFaction(IFaction faction)
    {
        Console.WriteLine($"玩家开始玩游戏,选择的是{faction.GetType().Name}");
        faction.Characteristic();
    }
}

场景中调用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var player = new Player();

            //玩家玩武当
            player.PlayFaction(new WuDang());

            //玩家玩少森
            player.PlayFaction(new ShaoLin());
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

很简单,运行结果就不展示了。玩什么,就调用什么,如果新增一个门派峨眉,代码如下所示:

public class EMei : IFaction
{
    public void Characteristic()
    {
        Console.WriteLine("峨眉派是治愈系的,医术很高,有起死回生之力。");
    }
}

场景中调用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var player = new Player();

            //新门派上线了,我要试试
            player.PlayFaction(new EMei());
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

   看到了吧,是不是很容易,加一个门派,只在场景中很少的改动,就新增了一个门派,都是通过扩展来实现的,原有的类都未进行改动过。这就是开闭原则,对扩展开放,对修改封闭。

   至此,六大原则就说完了,写出来还真是挺不容易的,比想象中的要困难的多,如有什么错误或问题讨论,多多指正和交流。后面的23种设计模式才是真枪实弹,坚持!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,747评论 0 14
  • 设计模式六大原则 设计模式六大原则(1):单一职责原则 定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类...
    viva158阅读 768评论 0 1
  • 参考资料:菜鸟教程之设计模式 设计模式概述 设计模式(Design pattern)代表了最佳的实践,通常被有经验...
    Steven1997阅读 1,174评论 1 12
  • 转载自 设计模式六大原则[http://www.uml.org.cn/sjms/201211023.asp#3] ...
    厨子阅读 1,096评论 2 5
  • TCP 编程 个人计算机或者服务器上通常会运行多个应用程序, 但是我们只需要一条网线就能连接互联网, 去访问互联网...
    江洋林澜阅读 313评论 0 0