OO设计原则:SOLID
OO指的是面向对象
【OO设计原则:SOLID】
- (
SRP
) 单一责任原则 - (
OCP
) 开放-封闭原则 - (
LSP
)Liskov
替换原则 - (
DIP
) 依赖转置原则 - (
ISP
) 接口聚合原则
【单一责任原则SRP】
-
含义:一个类改变的原因不应该超过一个。需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。
-
不应有多于1个的原因使得一个类发生变化。
-
一个类,一个责任。
-
如果一个类包含了多个责任,那么将引起不良后果:引入额外的包,占据资源;导致频繁的重新配置、部署等。
-
SRP是最简单的原则,却是最难做好的原则。
-
SRP的一个反例:
【(面向变化的)开放/封闭原则OCP】
-
对扩展性的开放
- 模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化。
-
对修改的封闭
- 但模块自身的源代码是不应被修改的
- 扩展模块行为的一般途径是修改模块的内部实现
- 如果一个模块不能被修改,那么它通常被认为是具有固定的行为
-
关键解决方案:抽象技术。
- “软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭”,也就是说,使用继承和组合/委托改变类的行为
-
OCP的一个例子:
(修改前)
(修改后)
【Liskov替换原则】
-
子类型必须能够替换其基类型。
-
派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异。
-
Liskov’s 替换原则意思是:“子类型必须能够替换它们的基类型。“或者换个说法:”使用基类引用的地方必须能使用继承类的对象而不必知道它。” 这个原则正是保证继承能够被正确使用的前提。通常我们都说,“优先使用组合(委托)而不是继承”或者说“只有在确定是 is-a 的关系时才能使用继承”,因为继承经常导致”紧耦合“的设计。
【接口分离原则ISP】
- 含义:不能强迫客户端依赖于它们不需要的接口,只提供必需的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
- 客户端不应依赖于它们不需要的方法。
- 避免接口(被大量方法)污染。
- 避免”胖“接口。
- “胖”接口具有很多缺点。具有“胖”接口的类是其接口没有内聚性的类(不够聚合)。
- 胖接口可分解为多个小的接口;不同的接口向不同的客户端提供服务;客户端只访问自己所需要的端口。(而不是提供给客户模块一个大的接口)
【依赖转置原则DIP】
- 定义:
- 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
- 应该使用大量的接口和抽象!
- 依赖即委托。调用时/使用时尽量依赖接口而不是实现类。
- 委托的时候要通过接口建立联系,而非具体的子类。(这样可以很容易切换方法)
- 这个设计原则的亮点在于任何被DI框架注入的类很容易用mock对象进行测试和维护,因为对象创建代码集中在框架中,客户端代码也不混乱。有很多方式可以实现依赖倒置,比如像AspectJ等的AOP(Aspect Oriented programming)框架使用的字节码技术,或Spring框架使用的代理等。
- 高层模块不要依赖低层模块;
- 高层和低层模块都要依赖于抽象;
- 抽象不要依赖于具体实现;
- 具体实现要依赖于抽象;
- 抽象和接口使模块之间的依赖分离。
【总结】
- 抽象(abstraction):模块之间通过抽象隔离开来,将稳定部分和容易变化部分分开
- LSP:对外界看来,父类和子类是“一样”的;
- DIP:对接口编程,而不是对实现编程,通过抽象接口隔离变化;
- OCP:当需要变化时,通过扩展隐藏在接口之后的子类加以完成,而不要修改接口本身。
- 分离(Separation):Keep It Simple, Stupid (KISS)
- SRP:按责任将大类拆分为多个小类,每个类完成单一职责,规避变化,提高复用度;
- ISP:将接口拆分为多个小接口,规避不必要的耦合。
- 归纳起来:让类保持责任单一、接口稳定。
语法驱动的构造
【语法驱动的构造】
- 有一类应用,从外部读取文本数据,在应用中做进一步处理
- 输入文件有特定格式,程序需读取文件并从中抽取正确的内容
- 从网络上传输过来的消息,遵循特定的协议
- 用户在命令行输入的指令,遵循特定的格式
- 内存中存储的字符串,也有格式需要
- 使用grammar判断字符串是否合法,并解析成程序里使用的数据结构
- 通常是递归的数据结构
【语法】
-
语法中的产物有这种形式:
nonterminal ::= expression of terminals, nonterminals, and operators
(这个 nonterminal 通常被称为根节点) -
产生式表达式中最重要的三个操作符:(注意
::
)- 连接:
x ::= y z
- 重复:
x ::= y*
X匹配零个或多个y - 联合:
x ::= y | z
X匹配y或z
- 连接:
-
其他操作符
?
表示出现0次或1次+
表示至少出现1次(= xx*
)[...]
表示包含方括号中列出的任何字符的长度为1的字符串[^...]
表示包含括号中未列出的任何字符长度为长度为1的字符串
-
* ? +
优先级最高,连接
次之,|
最低 -
终结符用单引号
-
注意检查为空情况
【正则语法和正则表达式】
-
正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点。
-
正则表达式去除引号和空格,从而表达更简洁(更难懂))
markdown ::= ([^_]* | '_' [^_]* '_' )*
简化为markdown ::= ([^_]*|_[^_]*_)*
-
正则表达式里的特殊操作符
.
任何单个字符\d
任何数字(即[0-9]
)\s
任何空白字符,包括空格、制表符、换页符等\w
任何字符包括下划线 (等价于[a-zA-Z_0-9]
)\
转义运算符 ,使其与字面上匹配(在Java中写需要两个\\
一个给Java语言检查,一个给语法解析用)
【在Java中使用正则表达式】
-
适用场合:我们用正则表达式匹配字符串(例如
String.split
,String.matches
,java.util.regex.Pattern
) -
java.util.regex
由以下三部分组成:- Pattern对象是对正则表达式进行编译之后得到的结果
- Matcher对象:利用Pattern对输入字符串进行解析
- PatternSyntaxException
-
例子:
-
用一个空格代替所有的多个空格:
String singleSpacedString = string.replaceAll(" +", " ");
-
匹配一个URL:
Pattern regex = Pattern.compile("http://([a-z]+\\.)+[a-z]+(:[0-9]+)?/"); Matcher m = regex.matcher(string); if (m.matches()) { // then string is a url }
-
提取HTML标签的一部分:(匹配双引号用
\"
)Pattern regex = Pattern.compile("<a href=\"([^\"]*)\">"); //可匹配如a href="(abc)"> Matcher m = regex.matcher(string); if (m.matches()){ string url = m.group(1); // Matcher.group(n) returns the nth parenthesized part of the regex }
-