前言
即使类的设计很糟糕,也还是有可能实现一个应用程序,使之运行并完成所需的工作。一个已完成的应用程序能够运行,但并不能表明程序内部的结构是否良好。 当维护程序员想要对一个已有的软件做修改的时候,问题才会浮现出来。比如,程序员试 图纠正已有软件的缺陷,或者为其增加一些新的功能。显然,如果类的设计良好,这个任务就 可能很轻松;而如果类的设计很差,那就会变得很困难,要牵扯大量的工作。 在大的应用软件中,这样的情形在最初的实现中就会发生了。如果以不好的结构来实现软 件,那么后面的工作可能变得很复杂,整个程序可能根本无法完成,或者充满缺陷,或者花费 比实际需要多得多的时间才能完成。在现实中,一个公司通常要维护、扩展和销售一个软件很 多年,很可能今天在商店买到的软件,其最初的版本是在十多年前就开始了的。在这种情形下, 任何软件公司都不能忍受不良结构的代码。 既然很多不良设计的效果会在试图调整或扩展软件时明显地展现出来,那么就应该以调整 或扩展软件来鉴别和发现这样的不良设计。
知识点:
面向对象程序设计的一些基本原则:
-
除代码复制 :
相同的代码抽取封装成一个函数
消除代码复制的两个基本手段,就是函数和父类。
-
1.封装: 降低耦合
正宗OOP的方案:“让双方都不了解双方,只知道你能干嘛,你能给我什么,你给我就好,我懒得自己拿”
(当修改一个类时,另一个类不需要联动修改,别让一个类大量使用另一个类的成员变量,别让两个类都有大量的代码和某个类的成员变量相关)- 程序设计的目标是一系列通 过定义明确的接口通信来协同工作的类。
- 耦合度反映了这些类联系的紧密度。
- 我们努力要获得 低的耦合度,或者叫作[松耦合(loose coupling)]。
- 耦合度决定修改应用程序的容易程度。
- 在一个松耦合的系统中,常常可以修改一个类,但同时不会修改其他类,而且 整个程序还可以正常运作。
- 聚合与程序中一个单独的单元所承担的任务的数量和种类相对应有关,它是针对类或方法 这样大小的程序单元而言的理想情况下,一个代码单元应该负责一个聚合的任务(也就是说,一个任务可以被看作是 一个逻辑单元)。
- 一个方法应该实现一个逻辑操作,而一个类应该代表一定类型的实体。
- 聚合 理论背后的要点是重用:如果一个方法或类是只负责一件定义明确的事情,那么就很有可能在 另外不同的上下文环境中使用。
自己的成员不要让别人来操作,对方需要什么,自己学会一个方法给他,别让别人懂那么多东西
封装----把流浪在外地的代码带回家管理:降低耦合的一个体现就素其他类不会直接用到我的成员变量,他需要利用我的成员变量干嘛,我就干嘛,不让外人来捣鼓我的东西,最后把结果告诉外人就行。
-
2.可扩展性:
可扩展性的意思就是代码的某些部分不需要经过修改就能适应将来可能的变化。
- 当添加一个可以归为同一类的类型时,如在一群子类中再添加一个平行子类,保证其他原有的使用到这些子类对象的代码不需要变动--->"以不变,应将来的万变"
- 优秀的软件设计者的一个素质就是有预见性。什么是可能会改变的?什么是可以假设在软 件的生命期内不会改变的? 在游戏的很多类中硬编码进去的一个假设是,这个游戏会是一个基于字符界面的游戏,通 过终端进行输入输出,会永远是这样子吗? 以后如果给这个游戏加上图形用户界面,加上菜单、按钮和图像,也是很有意思的一种扩展
-
聚合: -- 开放接口
- 自己能实现的自己说了算自己实现,然后告诉对方结果。
- 例如房间的门打开的方法,房间自己帮别人打开就行,置于我用手打开还是脚打开都自己话事,你不用管,反正门是给你打开了,不用你动手。
用接口实现聚合----给类实现新的方法,把具体细节彻底隐藏在该类中,只暴露结果的函数接口供外界使用,而如何实现该方法从此于外界无关。 - 以后若改动优化接口里面的实现逻辑代码,外部调用该接口的代码无需改动
-
灵活性:-- 硬编码,我们不约哦
用容器实现灵活性----替换掉"硬编码" === HashMap<Key,Value>的加入,可以将原本硬编码在类内部想同Value类型的成员变量通过Key-Value的方式替换,实现灵活加入Value对象的设计。
例如一个房子类House原本有一个房间Room no_a:需要一个房间类的成员变量,房号为变量名:Room no_a;当我们要为这个房子新建一个新房间时,则需要修改House类,添加一个新的成员变量Room no_b。当后续房子内又要添加新的房间时,则又需要修改House类。此时,House类的可扩展性就不高。当我们使用HaspMap<String,Room>来代替掉上述的Room成员变量,则外部需要给House添加新房子时,只需要把key当做原本的房间名,value作为新房间的对象,传入给House类的HashMap当中,就可以给House添加新房间了,而不需要改动House类,通过容器来实现是不是很灵活呢! - 维持接口:内部实现方式可以根据需要改变,而外部使用到该接口的程序可以不知道发生了什么。----层次设计理念
-
3.框架+数据:提高可扩展性
从程序中识别出框架和数据,以代码实现框架,将部分功能以数据的方式加载,这样能在很大程度上实现可扩展性。
- 目标:把程序的
硬编码
解成框架+数据
的结构。而原本硬编码的东西一般为成员变量、函数
。 - 框架:原本硬编码的东西,通过容器,做成一个框架----HashMap对应关系、接口函数。
- 数据:放在HaspMap(容器)中的键值对(内容)
-
成员变量 改成数据
//原成员变量 private Room room_a; private Room room_b; //改造: String类型的Key则表示room_a变量名,Room类型的Value则表示room_a 的引用对象 private HashMap<String,Room> rooms = new HashMap<String,Room>(); rooms.put("room_a",new Room()); //当需要用到room_a的时候,就 rooms.get("room_a")
- 函数(处理逻辑)改成数据
思路solution:上述将成员变量通过键值对的方式做成HashMap的item,是框架的基本思想。那么,参照这个基本思想,我们需要一个存放函数的HashMap,而Key、Value都是对象,方法却不是对象,怎么存进去呢?
登登登登---将方法封装到一个类里面,不就可以实例化一个对象放到HashMap里面了吗,然后通过类来调用辣个方法即可。因为HashMap通过泛型约束存放的对象,所依先构建一个父类,代表方法对象,例如叫Handler。不同的方法则设计为Handler的子类,则所有方法类都可以存进这个HashMap<String,Handler>里面,让这一个HashMap管理了:pass
应用:
第一步:消除代码复制
以一个类为单元,找到该类中重复或相似的代码段,封装成函数
第二步:封装 降低耦合--降低两个之间的联系
1、找其他类中是否直接用到了该类的成员变量,把这部分逻辑封装成成员函数,并把成员变量设为private,开放一个接口把结果返回给调用类。注意,给别人最需要的最小部分,如原本代码是System.out.println(balabalabala),那么只需要把这个balabala结果返回去即可,不要帮对方完成println等操作,重点:给数据
2、通过getter、setter给私有成员开放接口供外部使用。但这不是真正OOP的解决方案,这是无可奈何的过渡方案,这样是直接掏了心肝肺...)
第三步:聚合 -- 开放接口
(其实第二步封装已经做过聚合了)
自己能实现的己说了算自己实现,然后告诉对方结果。
例如房间的门打开的方法,自己帮别人打开就行,置于我用手打开还是脚打开都自己话事,你不用管,反正门是给你打开了,不用你动手。
第四步:框架 + 数据 --- 可扩展性
1、成员变量:找到类里面的成员变量,思考某类型的成员变量日后是否有增加的可能,例如House里面有一个房间Room,日后可能因为装修,多出几个房间,则把这类Room的成员变量改成用HashMap容器来存放
2、方法函数:将方法封装到一个方法类及其子类中,每个子类表示一个方法的实现。父类设计一个相同的接口,子类去实现不同的方法体。在调用方法的地方,用父类的变量调用父类设计的接口方法,虚拟机便会根据程序实际运行状态,调用对应子类里面的方法实现。
切记框架设计不要有例外,添加任何新的东西,都不要用硬编码来判断。
拾遗:
- 设计类的时候,类的成员首要思考为private,万不得已才public
- 要评判某些设计比其他的设计优秀,就得定义一些在类的设计中重要的术语,以用来讨论 设计的优劣。对于类的设计来说,有两个核心术语:耦合和聚合。
耦合
这个词指的是类和类之间的联系- 注意String -- StringBuffer的选用,当需要将大量字符串组合为一个字符串时,采用StringBuffer。若仅仅接收一个String对象,则使用String即可。
- 什么时候要设计传入this作为参数的方法?
当该方法不在this所指的那个类里面,又要用到那个类的成员,则需要设计出入this。而实际方法体的参数类型则为this所指的类型。而且这跟“动态”关系很大,而不是单纯的需要那个类的实例对象来进行操作,而是需要运行状态下的那个对象。因为同一个类的不同对象,成员变量是不一样的。(讲得又点绕,改天调整)
注意,代码中的汉字用的是UTF-8编码。请在Eclipse里将文件的编码格式改为UTF-8:
File --> Properties --> Resource (Text file encoding) ==> Other [UTF-8]
Eclipse导入工程乱码问题