面向对象设计原则

合集下载

请简述面向对象设计的启发规则

请简述面向对象设计的启发规则

请简述面向对象设计的启发规则在设计中,我们往往会根据自己已有的经验进行面向对象程序的编写。

但是实际上我们不知道为什么要这样做?我们可以怎样更好地改进面向对象设计呢?这个问题值得我们深入思考,也是我们本节课要解决的内容。

在开始之前,先给大家简单介绍一下面向对象程序设计的基本概念和几条设计规则。

1、主体是能被用户直接使用的工具,它对系统资源有使用权。

2、主体应该提供完成特定功能的信息,而不是说明完成某些特定功能所需要的过程和逻辑。

3、主体应该是对现实世界的抽象,而不是对人类经验的简单总结。

4、从属关系应该明确,如果两个主体间没有从属关系,那么从属关系就失去了意义。

5、复杂性应该控制在一定范围内。

6、如果存在可由子类扩充的方法,它也应该可以被子类继承。

7、通常情况下,应该为不同类型的方法分别提供相应的接口,方便它们互相调用。

8、面向对象的程序设计方法要求软件系统必须具备三个基本特性:封装性、继承性和多态性。

下面我们来看一个简单的例子,加深一下对这些概念的理解。

请读者帮助我们完善这个示例。

从上面的示例可以看出面向对象的程序设计方法适用于开发大规模软件系统,但是对于我们日常开发小规模程序来说还不够合适,所以我们在这里再来简化一下,并且通过进一步思考和分析,让学生自己来归纳和总结这些设计原则。

请同学们观察上面的例子,哪些符合了面向对象程序设计的启发规则?哪些又不符合?为什么?启发规则在各种各样的软件开发中都有存在,请大家想一想你在学习软件工程的时候,是否也曾经犯过上面的错误呢?在以后的软件工程教学过程中,我们可以利用各种各样的事物作为载体,把这些启发规则作为设计的准则贯穿于整个软件开发的过程当中,让学生能够轻松地掌握这些原则,真正做到举一反三,触类旁通。

3、对象只需要在主体中出现一次,而不需要其它任何操作。

4、主体可以被组合和重用。

5、如果程序中需要用到类或抽象类的话,那么应该尽量不重复使用。

6、如果子类可以扩展父类的功能,那么应该尽量少的使用重复的功能。

迪米特法则原则

迪米特法则原则

迪米特法则原则介绍迪米特法则(Law of Demeter)又称最少知识原则(Principle of Least Knowledge),是面向对象设计中的一条重要原则。

它强调对象之间的松耦合和封装性,旨在降低系统的复杂度和依赖关系。

本文将详细介绍迪米特法则的原理、应用场景以及如何遵循该原则进行设计和开发。

原则解析迪米特法则的核心思想是:一个对象应该尽可能少地了解其他对象的细节,只与其直接的朋友进行通信。

直接的朋友指的是当前对象的成员变量、方法的输入、输出参数中的对象。

迪米特法则要求我们在设计和开发过程中,尽量减少对象之间的交互,降低耦合度,提高系统的可维护性和可扩展性。

迪米特法则的优势遵循迪米特法则可以带来以下优势:1.降低耦合度:对象之间的交互减少,减少了对象之间的依赖关系,降低了耦合度,使系统更加灵活和可维护。

2.提高模块化:对象之间的关系更加清晰,每个对象只需关注与自己相关的内容,提高了模块的独立性和可重用性。

3.增强封装性:对象只需暴露必要的接口,隐藏内部细节,提高了封装性,减少了对外部的影响。

4.易于测试和调试:减少了对象之间的交互,使得测试和调试更加方便,定位问题更加容易。

迪米特法则的应用场景迪米特法则适用于以下场景:1.模块化设计:在模块化设计中,迪米特法则可以帮助我们划分模块的边界,减少模块之间的依赖,提高模块的独立性和可重用性。

2.系统架构:在系统架构设计中,迪米特法则可以帮助我们划分子系统和模块之间的边界,降低子系统和模块之间的耦合度,提高系统的灵活性和可扩展性。

3.面向对象设计:在面向对象设计中,迪米特法则可以帮助我们设计对象之间的关系,降低对象之间的交互,提高对象的封装性和内聚性。

遵循迪米特法则的实践方法遵循迪米特法则的实践方法如下:1.封装数据:将对象的数据封装在对象内部,通过提供公共接口进行访问,避免直接暴露对象的内部细节。

2.限制方法调用:对象之间的方法调用应该尽量少,只调用必要的方法,避免过多的交互和依赖。

软件工程第十一章面向对象设计

软件工程第十一章面向对象设计

THANKS
感谢观看
01
抽象类是一种不能被实例化的 类,它只能被其他类继承。
02
抽象类可以包含抽象方法和具 体方法。抽象方法是没有具体 实现的方法,需要在继承抽象 类的子类中实现。
03
通过继承抽象类,子类可以继 承抽象类的属性和方法,并且 可以重写或实现抽象类中的方 法。
接口与抽象类的选择
在设计软件时,选择使用接口还是抽象类取决于具体需求和设计目标。
关系
关系描述了对象之间的交互和联系。 常见的关系包括关联、聚合和继承。
继承与多态的设计
继承
继承是一种实现代码重用的方式,子类可以继承父类的属性和方法,并可以扩展或覆盖它们。通过继承,可以建 立类之间的层次结构,使得代码更加清晰和易于维护。
多态
多态是指一个接口可以有多种实现方式,或者一个对象可以有多种形态。多态可以提高代码的灵活性和可扩展性, 使得程序更加易于维护和修改。
02
类与对象的设计
类的定义与属性
类的定义
类是对象的抽象,它描述了一组具有相同属性和行为的对象。类定义了对象的结构、行为和关系。
属性
属性是类中用于描述对象状态的变量。每个对象都有其自己的属性值,这些属性值决定了对象的状态 。
对象的行为与关系
行为
行为是类中定义的方法,用于描述对 象可以执行的操作。方法定义了对象 的行为和功能。
高层模块不应该依赖于低层模块,它们都应 该依赖于抽象。
面向对象设计的优势
提高代码可重用性
通过类和继承实现代码重用,减少重 复代码。
提高代码可维护性
面向对象设计使得代码结构更加清晰, 易于理解和维护。
提高开发效率
通过快速原型开发,快速构建软件系 统。

《实用软件工程》第9章 面向对象设计

《实用软件工程》第9章 面向对象设计

• 信息隐藏:对于类而言,其内部信息如属性的表示方法和操作的实现算法,对 外界是隐藏的。外界通过有限的接口来访问类的内部信息。
17
9.3.2 面向对象设计的原则
• 低耦合:在面向对象设计中,耦合主要指对象之间相互关联的紧密程度,低耦 合有利于降低一个模块改变对其他模块的影响。
• 高内聚:内聚与耦合密切相关,低耦合往往意味着高内聚,高内聚有助于提高 系统独立性。
但随着需求理解的加深,以及对系统认识程度的逐步 提高,设计人员还要对模型进行修正和完善。 • 设计任务管理子系统包括确定任务,分配任务,还包 括权衡一致性、成本、性能等因素以及未来可扩充性。 • 设计数据管理子系统,需要设计数据格式以及相应的 服务,设计数据格式的方法与所用的数据存储管理模 式密切相关,不同数据存储管理模式时,属性和服务 的设计方法是不同的。
9.2 面向对象设计与面向对象分析的关系
• 设计阶段的任务是及时把分析阶段得到的需求转变成符合各项要求的 系统实现方案。与传统的软件工程方法不同的是,面向对象的方法不强调 需求分析和软件设计的严格区分。实际上,面向对象的需求分析和面向对 象的设计活动是一个反复迭代的过程,从分析到设计的过渡,是一个逐渐 扩充、细化和完善分析阶段所得到的各种模型的过程。严格的意义上来讲, 从面向对象分析到面向对象设计不存在转换问题,而是同一种表示方法在 不同范围的运用。面向对象设计也不仅仅是对面向对象分析模型进行细化。
• (2)人机交互子系统包括有效的人机交互所需的显示和输入,这些类在很大程度上 依赖于所用的图形用户界面环境,例如Windows、Delphi、C++,而且可能包括“窗 口”、“菜单”、“滚动条”、“按钮”等针对项目的特殊类。
25
9.5.1 系统分解

面向对象设计原则实验报告实验01

面向对象设计原则实验报告实验01

面向对象设计原则实验报告1.1实验目的1. 通过实例深入理解和掌握所学的面向对象设计原则。

2.熟练使用面向对象设计原则对系统进行重构。

3.熟练绘制重构后的结构图(类图)。

1.2实验内容1.在某绘图软件中提供了多种大小不同的画笔(Pen),并且可以给画笔指定不同颜色,某设计人员针对画笔的结构设计了如图1-1所示的初始类图。

通过仔细分析,设计人员发现该类图存在非常严重的问题,即如果需要增加一种新的大小或颜色的笔,就需要增加很多子类,例如增加一种绿色的笔,则对应每一种大小的笔都需要增加一支绿色笔,系统中类的个数急剧增加。

试根据依赖倒转原则和合成复用原则对该设计方案进行重构,使得增加新的大小或颜色的笔都较为方便,请绘制重构之后的结构图(类图)。

2.在某公司财务系统的初始设计方案中存在如图1-2所示的Employee类, 该类包含员工编号(ID),姓名(name),年龄(age).性别(gender)、薪水(salary)、每月工作时数( workHoursPerMonth),每月请假天数(leaveDaysPerMonth)等属性。

该公司的员工包括全职和兼职两类,其中每月工作时数用于存储兼职员工每个月工作的小时数,每月请假天数用于存储全职员工每个月请假的天数。

系统中两类员工计算工资的万法也不一样,全职员工按照工作日数计算工资﹐兼职员工按照工.作时数计算上资﹐内此在 Employee 类中提供了两个方法calculateSalaryByDays()和calculateSalaryByHours(),分别用于按照大数和时数计算工资,此外,还提供了方法displaySalary()用于显示工资。

试采用所学面向对象设计原则分析图1-2中Employee类存在的问题并对其进行重构绘制重构之后的类图。

3.在某图形界面中存在如下代码片段,组件类之间有较为复杂的相互引用关系:如果在上述系统中增加一个新的组件类,则必须修改与之交互的其他组件类的源代码,将导致多个类的源代码需要修改。

面向对象设计的三大原则,理解并能举例

面向对象设计的三大原则,理解并能举例

面向对象设计的三大原则,理解并能举例
面向对象编程设计有三大原则,分别是封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。

1. 封装(Encapsulation):封装是将数据和相关行为(方法)
组合在一个类中,以实现隐藏内部实现细节的原则。

通过封装,可以将一组数据和对它们的操作封装在一个类中,对外部只暴露必要的接口,隐藏了实现的细节,提高了代码的安全性和可维护性。

例如,一个汽车类可以封装了颜色、品牌、速度等变量和加速、刹车等方法,对外只提供加速和刹车的接口,而隐藏了内部细节。

2. 继承(Inheritance):继承是指创建一个新类(子类)从已
有的类(父类)中继承属性和方法的过程。

子类可以通过继承父类的特性来扩展和增强功能,并且可以重用已有的代码。

例如,有一个动物类,定义了一些公共属性和方法,然后创建了狗类和猫类继承动物类,狗类和猫类就可以共享动物类的一些功能,同时可以根据需要添加自己的特定功能。

3. 多态(Polymorphism):多态是指同一类对象在不同情况下
可以表现出不同的行为。

对象多态性使用继承和接口实现,通过动态绑定和方法重写,允许不同的对象对同一个方法做出不同的响应。

例如,一个动物类中有一个叫声的方法,猫类和狗类都继承了动物类,并重写了叫声的方法,当通过调用叫声方法时,猫和狗的叫声不同,实现了多态性。

这三个原则是面向对象设计的基石,有助于实现代码的可重用性、可扩展性和灵活性。

php面向对象程序设计(OOP)的61条准则

php面向对象程序设计(OOP)的61条准则
一个类用到的其他类的数目应当尽量少。
(23)尽量减少类和协作者之间传递的消息的数量。
(24)尽量减少类和协作者之间的协作量,也即:减少类和协作者之间传递的不同消息的数量。
(25)尽量减少类的扇出,也即:减少类定义的消息数和发送的消息数的乘积。
(26)如果类包含另一个类的对象,那么包含类应当给被包含的对象发送消息。也即:包含关系总是意味着使用关系。
(27)类中定义的大多数方法都应当在大多数时间里使用大多数数据成员。
(28)类包含的对象数目不应当超过开发者短期记忆的容量。这个数目常常是6。
当类包含多于6个数据成员时,可以把逻辑相关的数据成员划分为一组,然后用一个新的包含类去包含这一组成员。
(29)让系统功能在窄而深的继承体系中垂直分布。
(10)把不相关的信息放在另一个类中(也即:互不沟通的行为)。
朝着稳定的方向进行依赖.
(11)确保你为之建模的抽象概念是类,而不只是对象扮演的角色。
(12)在水平方向上尽可能统一地分布系统功能,也即:按照设计,顶层类应当统一地共享工作。
(13)在你的系统中不要创建全能类/对象。对名字包含Driver、Manager、System、Susystem的类要特别多加小心。
(41)所有的抽象类都应当是基类。
(42)所有的基类都应当是抽象类。
(43)把数据、行为和/或接口的共性尽可能地放到继承层次体系的高端。
(44)如果两个或更多个类共享公共数据(但没有公共行为),那么应当把公共数据放在一个类中,每个共享这个数据的类都包含这个类。
(45)如果两个或更多个类有共同的数据和行为(就是方法),那么这些类的每一个都应当从一个表示了这些数据和方法的公共基类继承。

面向对象设计六大原则

面向对象设计六大原则

面向对象设计六大原则面向对象设计的原则是面向对象思想的提炼,它比面向对象思想的核心要素更具可操作性,但与设计模式相比,却又更加的抽象,是设计精神要义的抽象概括。

形象地将,面向对象思想像法理的精神,设计原则则相对于基本宪法,而设计模式就好比各式各样的具体法律条文了。

面向对象设计原则有6个:开放封闭原则,单一职责原则,依赖倒置原则,Liskov替换原则,迪米特法则和接口隔离原则或合成/聚合复用原则(不同资料略有不同,这里对7个都做了整理)。

1单一职责原则(Single Responsibility Principle SRP)There should never be more than one reason for a class to change. 什么意思呢?所谓单一职责原则就是一个类只负责一个职责,只有一个引起变化的原因。

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化会削弱或抑制这个类完成其他职责的能力,这个耦合会导致脆弱的设计。

软件设计真正要做的许多内容,就是发现职责并把这些职责相互分离;如果能够想到多于一个动机去改变一个类,那么这个类就具有多于一个职责,就应该考虑类的分离。

以调制解调器为例如下图:从上述类图里面我们发现有四个方法Dial(拨通电话),Hangup(挂电话),Receive(收到信息),Send(发送信息),经过分析不难判断出,实际上Dial(拨通电话)和Hangup(挂电话)是属于连接的范畴,而Receive(收到信息)和Send(发送信息)是属于数据传送的范畴。

这里类包括两个职责,显然违反了SRP。

这样做有潜在的隐患,如果要改变连接的方式,势必要修改Modem,而修改Modem 类的结果导致凡事依赖Modem类可能都需要修改,这样就需要重新编译和部署,不管数据传输这部分是否需要修改。

因此要重构Modem类,从中抽象出两个接口,一个专门负责连接,另一个专门负责数据传送。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

面向对象设计原则单一职责原则--SRP一、SRP简介(SRP--Single-Responsibility Principle):就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。

所谓职责,我们可以理解他为功能,就是设计的这个类功能应该只有一个,而不是两个或更多。

也可以理解为引用变化的原因,当你发现有两个变化会要求我们修改这个类,那么你就要考虑撤分这个类了。

因为职责是变化的一个轴线,当需求变化时,该变化会反映类的职责的变化。

“就像一个人身兼数职,而这些事情相互关联不大,,甚至有冲突,那他就无法很好的解决这些职责,应该分到不同的人身上去做才对。

”二、举例说明:违反SRP原则代码:modem接口明显具有两个职责:连接管理和数据通讯;interface Modem{public void dial(string pno);public void hangup();public void send(char c);public void recv();}如果应用程序变化影响连接函数,那么就需要重构:interface DataChannel{public void send(char c);public void recv();}interface Connection{public void dial(string pno);public void hangup();}三、SRP优点:消除耦合,减小因需求变化引起代码僵化性臭味四、使用SRP注意点:1、一个合理的类,应该仅有一个引起它变化的原因,即单一职责;2、在没有变化征兆的情况下应用SRP或其他原则是不明智的;3、在需求实际发生变化时就应该应用SRP等原则来重构代码;4、使用测试驱动开发会迫使我们在设计出现臭味之前分离不合理代码;5、如果测试不能迫使职责分离,僵化性和脆弱性的臭味会变得很强烈,那就应该用Facade或Proxy模式对代码重构;开放封闭原则--OCP一、OCP简介(OCP--Open-Closed Principle):Software entities(classes,modules,functions,etc.) should be open for extension, but closed for modification。

软件实体应当对扩展开放,对修改关闭,即软件实体应当在不修改(在.Net当中可能通过代理模式来达到这个目的)的前提下扩展。

Open for extension:当新需求出现的时候,可以通过扩展现有模型达到目的。

Close for modification:对已有的二进制代码,如dll,jar等,则不允许做任何修改。

二、OCP举例:1、例子一假如我们要写一个工资税类,工资税在不同国家有不同计算规则,如果我们不坚持OCP,直接写一个类封装工资税的算税方法,而每个国家对工资税的具体实现细节是不尽相同的!如果我们允许修改,即把现在系统需要的所有工资税(中国工资税、美国工资税等)都放在一个类里实现,谁也不能保证未来系统不会被卖到日本,一旦出现新的工资税,而在软件中必须要实现这种工资税,这个时候我们能做的只有找出这个类文件,在每个方法里加上日本税的实现细节并重新编译成DLL!虽然在.NET的运行环境中,我们只要将新的DLL覆盖到原有的DLL即可,并不影响现有程序的正常运行,但每次出现新情况都要找出类文件,添加新的实现细节,这个类文件不断扩大,以后维护起来就变的越来越困难,也并不满足我们以前说的单一职责原则(SRP),因为不同国家的工资税变化都会引起对这个类的改变动机!如果我们在设计这个类的时候坚持了OCP的话,把工资税的公共方法抽象出来做成一个接口,封闭修改,在客户端(使用该接口的类对象)只依赖这个接口来实现对自己所需要的工资税,以后如果系统需要增加新的工资税,只要扩展一个具体国家的工资税实现我们先前定义的接口,就可以正常使用,而不必重新修改原有类文件!2、例子二下面这个例子就是既不开放也不封闭的,因为Client和Server都是具体类,如果我要Client 使用不同的一个Server类那就要修改Client类中所有使用Server类的地方为新的Server类。

class Client{Server server;void GetMessage(){server.Message();}}class Server{void Message();}下面为修改后符合OCP原则的实现,我们看到Server类是从ClientInterface继承的,不过ClientInterface却不叫ServerInterface,原因是我们希望对Client来说ClientInterface 是固定下来的,变化的只是Server。

这实际上就变成了一种策略模式(Gof Strategy)interface ClientInterface{public void Message();//Other functions}class Server:ClientInterface{public void Message();}class Client{ClientInterface ci;public void GetMessage()ci.Message();}public void Client(ClientInterface paramCi){ci=paramCi;}}//那么在主函数(或主控端)则public static void Main(){ClientInterface ci = new Server();//在上面如果有新的Server类只要替换Server()就行了. Client client = new Client(ci);client.GetMessage();}3、例子三使用Template Method实现OCP:public abstract class Policy{private int[] i ={ 1, 1234, 1234, 1234, 132 };public bool Sort(){SortImp();}protected virtual bool SortImp(){}class Bubbleimp : Policy{protected override bool SortImp(){//冒泡排序}}class Bintreeimp : Policy{protected override bool SortImp(){//二分法排序}}//主函数中实现static void Main(string[] args){//如果要使用冒泡排序,只要把下面的Bintreeimp改为BubbleimpPolicy sort = new Bintreeimp();sort.Sort();}三、OCP优点:1、降低程序各部分之间的耦合性,使程序模块互换成为可能;2、使软件各部分便于单元测试,通过编制与接口一致的模拟类(Mock),可以很容易地实现软件各部分的单元测试;3、利于实现软件的模块的呼唤,软件升级时可以只部署发生变化的部分,而不会影响其它部分;四、使用OCP注意点:1、实现OCP原则的关键是抽象;2、两种安全的实现开闭原则的设计模式是:Strategy pattern(策略模式),Template Methord (模版方法模式);3、依据开闭原则,我们尽量不要修改类,只扩展类,但在有些情况下会出现一些比较怪异的状况,这时可以采用几个类进行组合来完成;4、将可能发生变化的部分封装成一个对象,如: 状态, 消息,,算法,数据结构等等 , 封装变化是实现"开闭原则"的一个重要手段,如经常发生变化的状态值,如温度,气压,颜色,积分,排名等等,可以将这些作为独立的属性,如果参数之间有关系,有必要进行抽象。

对于行为,如果是基本不变的,则可以直接作为对象的方法,否则考虑抽象或者封装这些行为;5、在许多方面,OCP是面向对象设计的核心所在。

遵循这个原则可带来面向对象技术所声称的巨大好处(灵活性、可重用性以及可维护性)。

然而,对于应用程序的每个部分都肆意地进行抽象并不是一个好主意。

应该仅仅对程序中呈现出频繁变化的那部分作出抽象。

拒绝不成熟的抽象和抽象本身一样重要;Liskov替换原则--LSP一、LSP简介(LSP--Liskov Substitution Principle):定义:如果对于类型S的每一个对象o1,都有一个类型T的对象o2,使对于任意用类型T定义的程序P,将o2替换为o1,P的行为保持不变,则称S为T的一个子类型。

子类型必须能够替换它的基类型。

LSP又称里氏替换原则。

对于这个原则,通俗一些的理解就是,父类的方法都要在子类中实现或者重写。

二、举例说明:对于依赖倒置原则,说的是父类不能依赖子类,它们都要依赖抽象类。

这种依赖是我们实现代码扩展和运行期内绑定(多态)的基础。

因为一旦类的使用者依赖某个具体的类,那么对该依赖的扩展就无从谈起;而依赖某个抽象类,则只要实现了该抽象类的子类,都可以被类的使用者使用,从而实现了系统的扩展。

但是,光有依赖倒置原则,并不一定就使我们的代码真正具有良好的扩展性和运行期内绑定。

请看下面的代码:public class Animal{private string name;public Animal(string name){ = name;}public void Description(){Console.WriteLine("This is a(an) " + name);}}//下面是它的子类猫类:public class Cat : Animal{public Cat(string name){}public void Mew(){Console.WriteLine("The cat is saying like 'mew'"); }}//下面是它的子类狗类:public class Dog : Animal{public Dog(string name){}public void Bark(){Console.WriteLine("The dog is saying like 'bark'"); }}//最后,我们来看客户端的调用:public void DecriptionTheAnimal(Animal animal){if (typeof(animal) is Cat){Cat cat = (Cat)animal;Cat.Decription();Cat.Mew();}else if (typeof(animal) is Dog){Dog dog = (Dog)animal;Dog.Decription();Dog.Bark();}}通过上面的代码,我们可以看到虽然客户端的依赖是对抽象的依赖,但依然这个设计的扩展性不好,运行期绑定没有实现。

相关文档
最新文档