设计模式原则(1)--Single Responsibility Principle(SRP)--单一职责原则
1.定义:
不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
2.使用场景:
如果类A有两个职责:d1,d2。当职责d1需要修改时,可能会导致原本运行正常的职责d2功能产生问题。即如果一个类包含多种职责,就应该把类拆分。分别建立两个类A、B,让A负责d1,B负责d2。当需要修改某一职责,那么将不会对另外一个功能产生影响。
3.使用特点
一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。
4.实现案例
(1)用一个类秒数动物呼吸这个场景
class Animal { public void breathe(string animal){ Console.WriteLine(animal+"呼吸空气"); } } class Program{ static void Main(string[] args){ Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("猪"); animal.breathe("鱼"); Console.ReadLine(); } }
(2)我们发现不是所有动物都是呼吸空气的,比如鱼就是呼吸水的,根据单一职责原则,我们将Animal类细分为陆生动物类和水生动物类,如下所示:
class Terrestrial { public void breathe(string animal){ Console.WriteLine(animal+"呼吸空气"); } } class Aquatic{ public void breathe(string animal){ Console.WriteLine(animal + "呼吸水"); } } class Program { static void Main(string[] args){ Terrestrial terrestrial = new Terrestrial(); terrestrial.breathe("牛"); terrestrial.breathe("羊"); terrestrial.breathe("猪"); Aquatic aquatic = new Aquatic(); aquatic.breathe("鱼"); Console.ReadLine(); } }
(3)我们发现这样修改的花销很大,既要将原来的类分解,又要修改客户端。而直接修改Animal类虽然违背了单一职责原则,但花销小的多,如下所示:
class Animal{ public void breathe(string animal){ if ("鱼".Equals(animal)){ Console.WriteLine(animal + "呼吸水"); } else { Console.WriteLine(animal + "呼吸空气"); } } } class Program{ static void Main(string[] args){ Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("猪"); animal.breathe("鱼"); Console.ReadLine(); } }
(4)可以看到,这种修改方式简单的多。但却存在隐患,一天需要将鱼分为淡水鱼,海水鱼,又需要修改Animal类的breathe方法。可能给“猪牛羊”等相关功能带来风险,这种修改直接在代码级别违背了单一职责原则,虽然修改起来最简单,但隐患最大。还有一种修改方式:
class Animal{ public void breathe(string animal){ Console.WriteLine(animal + "呼吸空气"); } public void breathe2(string animal){ Console.WriteLine(animal + "呼吸水"); } } class Program{ static void Main(string[] args){ Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("猪"); animal.breathe2("鱼"); Console.ReadLine(); } }
这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然违背了单一职责原则,但在方法级别上却是符合单一职责原则的。那么在实际编程中,采用哪一种呢?我的原则是,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,才可以在方法级别违反单一职责原则。
5.优缺点:
(1)单一职责优点
[1]类的复杂性降低,实现什么职责都有清晰明确的定义;
[2]可读性提高,复杂性减低,可读性当然提高;
[3]可维护性提高,可读性提高,可维护性当然提高;
[4]变更引起的风险减低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的类有影响,对其他接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
6.注意事项
单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。
接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。