面向OOP的设计模式

在说设计模式之前我觉得很有必要说一说这段时间我到底学会了什么??

之前在没接触软件构造这门课的时候我觉得我有java编程基础,这课不就是信手拈来么,但从ADT,OOP,LSP这些原则学习过来,我感受最大的就是,原来我之前使用的java技术是基于如此抽象的原则给出的,在之前我只了解的是java的继承,多态等显式的技术,甚至在学习过程中我曾无数次疑问,真的有必要这么抽象么?会用不就得了。但是当你反复观看思考这些抽象原则的时候,你会觉得他真的很准确,虽然有些可能意思相似,但是掌握了之后真的会对我们具体在进行面向对象编程时,当然不只是java的实战中,有很重要的作用。

回到主题,我之前总结的两个外部指标,可复用性和可维护性,现在就要基于这两个性质学习设计模式了,那我这两个外部指标到底学会了啥啊??

在学习完ADT我们大致理解了一下抽象,这里简单认为是设计与实现分开,客户单纯使用ADT就能满足自己的需求,至于ADT咋实现的,程序员遵循那些特性保证ADT的安全性就好了,到了OOP就是具体的设计与实现分开,主要就是一个词抽象,根据抽象程度的不同又有了接口,抽象类,实现类的概念,那针对这三个概念衍生出来的就是多态,重写,重载等技术。紧接着就是可复用性和可维护性的介绍,这里我们把两个指标放在一起说。这里最重要的就是SOLID原则,而这五大原则也分为两类,一类是抽象,一类是分离

先说抽象,抽象技术中OCP可谓是基础了,说的就是一个软件应该对扩展开放,对修改关闭,那具体的实现技术就是抽象,一提到抽象之前说的三个抽象程度就出现了,我们使用最多就是接口了,这里也就引入了DIP,对接口编程,而不是对实现编程,通过抽象接口隔离变化,这里就能体现出我高层使用对接口编程,接口是不变的也就是修改关闭,而实现是针对接口的实现,可以有不同的实现,也就是扩展开放,那对扩展开放了,可维护性自然就好了,而且扩展。一但涉及到抽象程度,就会涉及到基类和子类,而基类与子类的继承关系就是抽象的具体实现,那这里就需要LSP作为保障了,也就是规范了实现抽象化的具体步骤了,你使用我的基类,我要让使用时子类和父类完全一样,这就是规范。这种规范在一定程度上就实现了可复用性。但是单纯以继承为基础的可复用性是不灵活的,因此引入委派,不要被委派的名字吓到,其实大家很常用的,就是再类内部直接表示一个对象,然后使用该对象的方法,继承和委派的组合在可复用性上使用的很多。

总的来说抽象说的是OCP,DIP,LSP,其中OCP是基础

再说一下分离,分离就比较好解释了,我们在进行可维护性的说明是,一个很重要的指标就是类之间的耦合度,这就引出了模块化编程,其核心就是低耦合,高内聚。说白了就是各司其职,密切合作,那面向这种分离首要的就是SRP,也就是每个模块是单一职责的,其次就是ISP,接口也要分割为合理大小,避免耦合。

这就是我对这5大原则的理解,其中既包含了可复用性,也包含了可维护性,面对这5大原则,我们不可能完全分隔开,一个好的面向对象的编程,这些都是不可缺少了,对具体的外部指标我们只需要理解其特性与评价标准,然后结合抽象分离两大武器去具体的结合一下,充分理解SOLID原则才是重要的,接下来,理解了这些原则,一些好的设计模式才会派上用场,每种设计模式都是SOLID原则中一种或几种的组合,下面我再展开设计模式的说明。

以老师讲的为主,这里主要分三类,七种模式

1.creational patterns(创建型模式)

1.1Factory method pattern(工厂方法模式)

工厂模式就是就是对OCP原则和DIP原则的充分展现,客户端(高层次模块)只依赖抽象,不依赖于具体(低层次模块),而且面对扩展是开放的,只需要新建一个工厂,而客户端的修改是关闭的。

客户端正常情况下如果需要一个对象需要直接new,但限制是需要知道具体类的名字,但如果不知道/不确定/不想在客户端中指明的话就需要工厂方法模式。

大致实现

定义一个用于创建对象的接口(工厂),有抽象方法返回一个实现类,让该接口的子类型来决定实例化哪个类,对于需要实例化的类也需要先使用接口统一,这里也有DIP和LSP的体现。当加入新的实现类时,只需要添加新的工厂,或者修改已有的工厂类返回新的实现类即可,不会影响客户端代码。

也可以不创建很多的工厂而是根据类型决定new哪个具体的实现类。

当然也可以使用静态方法,直接使用类进行创建对象,优点就是只创建一个对象。

工厂方法也有优点和缺点:

优点:无需将特定的类绑定到代码中,只需要接口就能得到对象,符合OCP原则,简单说就是解耦合,可扩展性好。

缺点:每增加一个产品就需要添加一个工厂子类去产生特定的产品,或者在统一中添加代码。

2.Structural patterns(结构性模式)

2.1Adapter(适配器模式)

将某个类/接口转换成client希望的其他形式,这个当然也符合OCP原则,我们不去修改原来的类/接口,而是添加一个适配器也就是扩展开放,去满足client的要求,自然也就满足DIP原则,面向适配器接口

大致实现:

通过添加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类,这里的封装一般有两种实现方式,继承或者委派,其实就是复用加上修改,符合client的要求。这里面也有使用到DIP原则,因为是面向接口进行编程的,接口的实现中可能是把任务委派给被适配的类,或者继承以达到一定的复用效果,也就是适配了。

个人认为这里使用委派更佳易于理解,而且灵活,对不需要的接口也不需要继承。 

2.2Decorator(修饰器模式)

修饰器模式是使用比较广泛,其主要解决的时每个子类实现不同功能,如何将特性的任意组合的问题。

传统想法可能是继承,但深层次的继承对代码的可维护性是不好的,会出现组合爆炸,大量代码重复。

大致实现:

这里主要使用递归的思想,对原始的对象通过委派的机制,将对每个特性构造的子类加到对象身上,为对象添加特性,类似修饰。

这个图很重要,我们逐个接口/类解析

component:定义修饰物和被修饰物的执行公共操作

concretecomponent:其实对象,在此基础上添加功能。

Decorator:抽象类,所有修饰类的父类,他实现共性的功能,同时包含一个被保护的component,用来指向被修饰的对象

concreteDecorator:具体的修饰类,可以添加功能,可以任意添加

每次添加一个修饰类时就把当前的component赋值为上一个,依次嵌套,调用方法时反正调用。

下面是一个例子:(stack)

 

在客户端使用的时候每次需要一个新功能是只需要去加上一层修饰,也就是将已有的stack赋给新的具体修饰类。

 3.Behavioral patterns(行为性模式)

3.1Strategy(策略模式)

有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码中。

就像是排序算法有很多种,在现实排序时,可以根据不同排序算法的特点不同,client动态的选择排序算法。

该策略很好的使用了OCP,DIP,LSP。主要就是面向接口编程,即对扩展开发,有对修改关闭,并且满足替换原则。

大致实现:

为不同的实现方法构造抽象接口,利用临时性的委派以及组合,通过参数传递,将需要的特定算法传入,并且结合子类型多态,解决client的功能请求。

这里是不是觉得似曾相识,当然了之前使用的frame框架系统层次的复用,其中黑盒框架就是以策略模式为基础的,抽象接口中给出程序员需要完成的个性化的方法,在框架中留出接口可将程序员写的特定的实现传入进而进而调用。

3.2Template Method(模板模式)

就像做事情的步骤一样,有些事情的步骤时完全一样的,只不过具体的方法可能有所区别

该策略很好的使用了OCP,DIP,LSP。主要就是面向接口编程,即对扩展开发,有对修改关闭,并且满足替换原则。

大致实现:

共性的步骤在抽象类内部公共实现,差异化的步骤用抽象方法给出在各个子类中实现。具体的实现技术就是使用继承和重写

换句话说,模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

乍一看有点像可复用性中的系统层次的frame框架复用啊,当然眼熟了白盒子框架就是以模板模式为基础的,先在框架中把共性的步骤写好,然后留出抽象方法的实现,把个性的方法留出来给程序员实现,程序员继承该框架,然后在框架中重写抽象方法,实现模板。 

3.3Iterator(迭代器模式)

客户端希望对放入容器的一组ADT对象进行遍历访问,无序关心容器的具体类型

该策略很好的使用了OCP,DIP,LSP。主要就是面向接口编程,即对扩展开发,有对修改关闭,并且满足替换原则。我认为这里有体现出分离的思想,具体的遍历任务由具体的类完成,体现了RSP。

大致实现:

interface Iterator 定义标准的遍历协议:由三个方法构成

Concrete Iterator 针对具体遍历类的迭代器,具体实现接口的三个方法

interface Aggregate(Iterable)实现该接口的容器对象是可迭代的,必须实现一个返回迭代器的方法。有些类似工厂方法模式,返回一个iterator实例

Concrete Aggregate 真正可迭代的容器类,实现interface Aggregate

总的来说,就是集合类实现Iterable接口,并在实现自己独特的Iterator迭代器,允许客户端利用特定的迭代器返回方法得到针对该集合类的迭代器,然后显示(while)或隐式(for)进行迭代遍历。

3.4Visitor(访问者模式)

前面介绍的迭代器模式主要是针对同一种数据类型的迭代,而现实中对一组数据的访问,很有可能数据类型不一致,那访问的方式也就不同,这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便,访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

对于特定类型的object的特定操作,在运行时将二者动态绑定到一起,该操作可以灵活多变,无需更改被visit的类。

本质上:将数据和作用与数据上的特定操作分离开

该策略很好的使用了OCP,DIP,LSP。主要就是面向接口编程,即对扩展开发,有对修改关闭,并且满足替换原则。

大致实现:

为ADT预留出接口ItemElement,用于将来的可扩展功能的接入点(accept(Visitor visitor)),外部实现代码(ConcreteVisitor)可以在不改变ADT本身的情况下在需要时通过delegation接入ADT,将真正的操作留给Visitor接口的实现类

Element:对要处理的多个element统一接口,提供accept抽象方法,实现该接口的类都是可以进行visit的

Visitor:多复杂数据元素的visit操作的抽象集合,对每一个Element都需要有visit方法。

ConcreteVisitor:针对数据元素集合的visit的具体操作,可以有多种操作,达到扩展的目的。

这里简单的简单的比较一下Visitor和Iterator。

其实都是对元素集合的访问,Iterator是针对相同数据类型的访问,将遍历的功能委派给Iterator对象。Visitor是针对不同数据类型的访问,数据集合的访问操作比较稳定,但是由于数据结构的不同,访问的方式也不同,因此就需要将访问方法独立出来委派给Visitor抽象接口及其实现子类进行实现,而且可以扩展访问方式,而不影响ADT。

简单比较一下Strategy和Visitor

两个存在共性都是通过委派动态的建立对象之间的联系,但是Visitor强调外部定义某种对ADT的操作,而Strategy主要是在内部进行算法的灵活替换,两者都有极好的可扩展性,并且是修改封闭的,面向接口编程,符合OCP,DIP。

也许大家发现分离这门强大的武器在OOP这些设计模式中使用的不是很多啊,其实恰恰相反,我认为抽象与分离是秘不可分的,我们在ADT的设计中就已经体现出了分离的思想,只有做到了很好的分离,才能更佳方便的去讨论抽象,可以说分离与抽象是相辅相成的,缺一不可,虽然没有显示的表示出,但是我们在讨论不同模块的功能性时,已经在使用分离的思想了。

以上就是我对OOP编程的5大原则并结合7种主要设计模式的思考,希望大家可以批评指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值