概述
七大设计原则
- 设计原则
- 设计模式
- 1.Open-Closed Principle 开闭原则
- 2.Dependence Inversion Principle 依赖倒置原则
- 3.Simple Responsibility Principle 单一职责原则
- 4.Interface Segregation Principle 接口隔离原则
- 5.Law of Demeter 迪米特原则
- 6.Liskov Substitution Principle 里氏替换原则
- 7.Composite/Aggregate Reuse Principle 合成复用原则
设计原则
设计模式
在研究设计原则之前先引入设计模式,那什么是设计模式?
- 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Designpattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
- 设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度,使程序呈现高内聚,低耦合的特性。
- 设计模式并不局限于某种语言,java,php,c++都有设计模式。
- 一堆优秀代码的集合。
注意:千万不要认为有任何一种设计模式,能解决任何问题,每一种设计模式只能用于适用的场景,而不是万能的。
1.Open-Closed Principle 开闭原则
- Open-Closed Principle, OCP
- 核心思想:对扩展开放(对提供方),对修改关闭(对使用方)
- 扩展开放:模块添加新功能,不改变原有的代码
- 修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块是修改关闭的
- 通俗理解就是添加一个功能应该是在已有的代码基础上进行扩展,而不是修改已有代码
开闭原则是最基础的设计原则,其他设计原则都是开闭原则的具体形态
2.Dependence Inversion Principle 依赖倒置原则
- Dependence Inversion Principle, DIP
- 核心思想:要依赖于抽象,不要依赖于具体的实现 (上层不能依赖于下层,他们都应该依赖于抽象)
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
示例1:
class RichPerson{
// 人依赖于动物
public void feed(ZangAo za){
System.out.println("开始喂养。。。");
za.eat();
}
public void feed(Ostrich os){
System.out.println("开始喂养。。。");
os.eat();
}
public void feed(YangTuo yt){
System.out.println("开始喂养。。。");
yt.eat();
}
}
class ZangAo{
public void eat(){
System.out.println("藏獒一顿吃十几斤肉类");
}
}
class Ostrich{
public void eat(){
System.out.println("鸵鸟吃植物类为生");
}
}
class YangTuo{
public void eat(){
System.out.println("羊驼最喜欢吃生玉米");
}
}
// #=================================================================
public class Client {
public static void main(String[] args) {
RichPerson person = new RichPerson();
ZangAo za = new ZangAo();
Ostrich os = new Ostrich();
person.feed(za);
person.feed(os);
}
}
假设示例1中业务要求再养一头羊驼,在扩展的同时同样是违背了开闭原则,因为不满足对使用方的修改关闭原则。改进后的代码如下:
示例2:
// 改进 抽象
interface Animal{
public void eat();
}
//使用方
class RichPerson{
//依赖于抽象
public void feed(Animal animal){
System.out.println("开始喂养。。。");
animal.eat();
}
}
//具体实现
class ZangAo implements Animal{
public void eat(){
System.out.println("狗啃骨头");
}
}
//具体实现
class Ostrich implements Animal{
public void eat(){
System.out.println("猫吃鱼。。。");
}
}
// 来变化:
class Gecko implements Animal{
@Override
public void eat() {
System.out.println("壁虎爱吃蚊子。。。");
}
}
public class Client {
public static void main(String[] args) {
RichPerson person = new RichPerson();
ZangAo dog = new ZangAo();
Ostrich cat = new Ostrich();
Gecko gk = new Gecko();
person.feed(dog);
person.feed(cat);
person.feed(gk);
}
}
示例2中改进后让RichPerson依赖于抽象的Animal这样满足了开闭原则的同时也体现了依赖倒置原则。
3.Simple Responsibility Principle 单一职责原则
- Single Responsibility Principle, SRP
- 每个方法、每个类、每个框架都只负责一件事情
- 核心思想:解耦和增强内聚性(高内聚,低耦合)
- 类被修改的几率很大,因此应该专注于单一的功能。如果你把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题
示例1:
public class Client {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1)
drawRectangle(s);
else if (s.m_type == 2)
drawCircle(s);
else if (s.m_type ==3)
drawTriangle(s);
}
// Shape 与 GraphicEditor 依赖关系
//绘制矩形
public void drawRectangle(Shape r) {
System.out.println(" 绘制矩形 ");
}
//绘制圆形
public void drawCircle(Shape r) {
System.out.println(" 绘制圆形 ");
}
//绘制三角形
public void drawTriangle(Shape r) {
System.out.println(" 绘制三角形 ");
}
}
//Shape类,基类
class Shape {
int m_type;
}
//矩形
class Rectangle extends Shape {
Rectangle()
{
super.m_type = 1;
}
}
//圆形
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
//三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
}
假设现在的需求为画出三角形,那么在示例1的基础上我们势必会改动使用方的代码,虽然示例1满足对扩展开放,但是不满足对修改关闭,所以违背了开闭原则。再具体点就是GraphicEditor类作为接口的调用者又干了绘图动作的事,违背了单一职责原则。那么该如何优化呢?
示例2:
public class Client {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Other());
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
//图形里面直接提供绘画方法
s.draw();
}
}
//Shape类,基类
abstract class Shape {
int m_type;
public abstract void draw();
}
//矩形
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形 ");
}
}
//圆形
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
//三角形
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
//业务变化: 增加图形
class Other extends Shape{
@Override
public void draw() {
System.out.println("绘制其他图形");
}
}
要想不违背对使用方修改关闭原则中的单一职责原则,那么最好的解决办法就是将绘制图形的动作提前,即绘制方法直接写到图形类里由自己完成。示例2将基类改为一个抽象类,内部写了一个draw()方法,现在如果又有新需求让增加其它图形,那么新增加的图形类直接去继承这个基类重写draw()方法就可以了,绘制图形的动作让每个图形类自己去完成,这样就完美的解决了单一职责原则问题。
4.Interface Segregation Principle 接口隔离原则
- Interface Segregation Principle, ISP
- 核心思想:不应该强迫客户程序依赖他们不需要使用的方法
- 客户端不应该依赖它不需要的接口
- 类间的依赖关系应该建立在最小的接口上
- 一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口当中
- 单一在于实现,接口隔离在于抽象
示例1:
//反例
interface I {
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
//依赖
class A{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method2();
}
public void depend3(I i){
i.method3();
}
}
class B implements I{
public void method1() {
System.out.println("类B实现接口I的方法1");
}
public void method2() {
System.out.println("类B实现接口I的方法2");
}
public void method3() {
System.out.println("类B实现接口I的方法3");
}
//对于类B来说,method4和method5不是必需的,但是由于接口I中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
public void method4() {}
public void method5() {}
}
class C{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method4();
}
public void depend3(I i){
i.method5();
}
}
class D implements I{
public void method1() {
System.out.println("类D实现接口I的方法1");
}
//对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
public void method2() {}
public void method3() {}
public void method4() {
System.out.println("类D实现接口I的方法4");
}
public void method5() {
System.out.println("类D实现接口I的方法5");
}
}
public class Client{
public static void main(String[] args){
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend2(new D());
c.depend3(new D());
}
}
针对这种情况的解决办法,即顺从接口隔离原则的改进方法如下:
示例2:
public class Client {
}
interface I1 {
public void method1();
}
interface I2 {
public void method2();
public void method3();
}
interface I3 {
public void method4();
public void method5();
}
class A{
public void depend1(I1 i){
i.method1();
}
public void depend2(I2 i){
i.method2();
}
public void depend3(I2 i){
i.method3();
}
}
class B implements I1, I2{
public void method1() {
System.out.println("类B实现接口I1的方法1");
}
public void method2() {
System.out.println("类B实现接口I2的方法2");
}
public void method3() {
System.out.println("类B实现接口I2的方法3");
}
}
class C{
public void depend1(I1 i){
i.method1();
}
public void depend2(I3 i){
i.method4();
}
public void depend3(I3 i){
i.method5();
}
}
class D implements I1, I3{
public void method1() {
System.out.println("类D实现接口I1的方法1");
}
public void method4() {
System.out.println("类D实现接口I3的方法4");
}
public void method5() {
System.out.println("类D实现接口I3的方法5");
}
}
示例2将接口进行合理的拆分后,满足了接口隔离原则。
5.Law of Demeter 迪米特原则
- Demeter Principle
- 又称最少知道原则
- 核心思想:一个对象应当对其他对象有尽可能少的了解,不和陌生人说话(只与直接的朋友通信)
- 降低各个对象之间的耦合,提高系统的可维护性
- 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部
示例1:
public class Client {
public static void main(String[] args) {
}
}
class Computer {
public void savaData() {
System.out.println("保存数据。。。");
}
public void killProcess() {
System.out.println("关闭程序。。。");
}
public void closeScreen() {
System.out.println("关闭屏幕。。。");
}
public void stopPower() {
System.out.println("停止供电。。。");
}
}
class Person {
// 关联
private Computer cp = new Computer();
//关电脑
public void closeComputer() {
cp.savaData();
cp.killProcess();
cp.stopPower();
cp.closeScreen();
}
}
示例1中的表述是人关电脑的操作,人关电脑实际上只需按一个关机键即可,根本不需要知道怎么一步一步去关电脑,所以在示例1的反例中恰巧违背了这一思想。改进后的代码如下:
示例2:
public class Client {
}
class Computer {
private void savaData() {
System.out.println("保存数据。。。");
}
private void killProcess() {
System.out.println("关闭程序。。。");
}
private void closeScreen() {
System.out.println("关闭屏幕。。。");
}
private void stopPower() {
System.out.println("停止供电。。。");
}
//关机按钮
public void shutDown(){
savaData();
killProcess();
closeScreen();
stopPower();
}
}
class Person {
private Computer cp = new Computer();
// 按 按钮 直接关机了 作为调用者 不关注 服务方的具体细节
public void closeComputer() {
cp.shutDown();
}
}
示例2中关机过程交给电脑去执行,而人只需按一个关机键就行了,作为调用者无需关注服务方的具体细节,这就是迪米特原则。
6.Liskov Substitution Principle 里氏替换原则
- Liskov Substitution Principle, LSP
- 核心思想:任何父类出现的地方,子类都可以替代出现。换句话讲就是子类可以扩展父类的功能,但不能改变父类原有的功能。
- 也就是说,子类对象可以随时随地的替换父类对象,且替换完以后,语法不会报错,业务逻辑也不会出现问题
- 所有引用基类的地方必须能透明地使用其子类的对象
- 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题
多态里面的向上转型 、匿名内部类都是基于里氏替换原则
示例1:
public class LiskovSubstitutionTest {
public static void main(String[] args) {
XxxService service = new XxxService(new CacheManagerService());
service.doXxxService("key1");
service.doXxxService("key2");
}
}
class XxxService {
// 缓存,模拟环境
private CacheManagerService cm;
public XxxService(CacheManagerService cm) {
this.cm = cm;
}
// 业务
public String doXxxService(String key) {
return cm.get(key);
}
}
// 单机JVM的缓存服务
class CacheManagerService {
// 没有考虑并发场景,模拟环境
static Map<String, String> cache = new HashMap<>();
static {
cache.put("key1", "value1");
}
/**
* 获取
*
* @param key
* @return 如果缓存返回值为null或者"" 则直接返回null
*/
public String get(String key) {
//只能保证百分之九十九的情况下数据的准确性
String s = cache.get(key);
System.out.println("父类没有判断,直接返回 : " +s);
return s;
}
// 添加
public void put(String key, String value) {
cache.put(key, value);
}
}
示例1中假设出现了突发情况,导致了缓存数据的不一致性,某程序员为了解决数据不一致性问题采用了如下方案:
示例2:
public class LiskovSubstitutionTest {
public static void main(String[] args) {
XxxService service = new XxxService(new RedisCentralCacheManagerService());
service.doXxxService("key1");
service.doXxxService("key2");
}
}
class XxxService {
// 缓存,模拟环境
private CacheManagerService cm;
public XxxService(CacheManagerService cm) {
this.cm = cm;
}
// 业务
public String doXxxService(String key) {
return cm.get(key);
}
}
// 单机JVM的缓存服务
class CacheManagerService {
// 没有考虑并发场景,模拟环境
static Map<String, String> cache = new HashMap<>();
static {
cache.put("key1", "value1");
}
/**
* 获取
*
* @param key
* @return 如果缓存返回值为null或者"" 则直接返回null
*/
public String get(String key) {
//只能保证百分之九十九的情况下数据的准确性
String s = cache.get(key);
System.out.println("父类没有判断,直接返回 : " +s);
return s;
}
// 添加
public void put(String key, String value) {
cache.put(key, value);
}
}
// 基于Redis的集中式缓存管理
class RedisCentralCacheManagerService extends CacheManagerService {
// Redis实现
static Map<String, String> redisCache = new HashMap<>();
// 未考虑并发情况,直接使用HashMap当容器
private static Map<String, String> db = new HashMap<>();
static {
redisCache.put("key1", "value1");
db.put("key2","value2");
}
@Override
public String get(String key) {
//解决数据准确性问题,比如redis数据缓存失效,
// 但是此时定时任务没有触发db重新将数据刷入缓存这百分之一情况
String cacheValue = redisCache.get(key);
if(cacheValue == null){
//没有就从数据库里面取
String s = db.get(key);
System.out.println("子类做了判断,从数据库查询返回 : " +s);
//更新redis
redisCache.put(key,s);
return s;
}
System.out.println("子类也有数据,查询返回 : " +cacheValue);
return cacheValue;
}
}
示例2中子类较父类保证了数据的准确性,但是子类重写了父类方法且修改了方法内容,违背了里氏替换原则。该示例为反例。
7.Composite/Aggregate Reuse Principle 合成复用原则
- Composite Reuse Principle, CRP
- 核心思想:尽量使用对象组合,而不是继承来达到复用的目的(组合优于继承)
- 继承关系是强耦合,组合关系是低耦合
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力
最后
以上就是神勇豌豆为你收集整理的七大设计原则源码详解设计原则的全部内容,希望文章能够帮你解决七大设计原则源码详解设计原则所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复