六大原则简单描述
- 单一职责原则:一个类中应该是相关性很强的一类函数。
- 开闭原则:对扩展开放,对修改封闭。实现扩展的方法是使用抽象和依赖注入。将可扩展的功能抽象,然后通过外部注入具体实现。
- 里氏替换原则:所有父类都能被子类替换,并且不影响程序的正确运行。核心是建立抽象,结合开闭原则,对扩展开放,对修改关闭。
- 依赖倒置原则:面向抽象编程。高层模块是调用方,底层模块是实现方。模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。核心要点:
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节:接口或抽象类,不用在意具体的实现
- 细节应该依赖抽象:由实现类去具体实现抽象
- 接口隔离原则:客户端依赖的接口应尽可能的小。比如:实现一个工具类来关闭所有支持
Closeable的对象,该工具类就应该只依赖Closeable这个抽象的接口,而不是OutputStream等具体的实现或与 Stream 相关的其他接口 - 迪米特原则:一个对象应该对其他对象有最少的了解,只与直接的朋友通信。尽量保持类的依赖关系是一条没有交叉,没有环的线。
模拟一个图片加载的库 ImageLoader ,做应用示例. 下面是类图:
@startuml
interface ImageCache{
+ Bitmap get(String url)
+ void put(String url, Bitmap b)
}
interface Loader{
+ Bitmap load(String url)
}
ImageCache <|-- MemoryCache
ImageCache <|-- DiskCache
Loader <|-- SyncLoader
Loader <|-- AsyncLoader
class ImageLoader{
- ImageCache imageCache
- Loader loader
+ void display(String url, ImageView iv)
+ void setImageCache(ImageCache cache)
+ void setLoader(Loader loader)
}
ImageLoader --o ImageCache
ImageLoader --o Loader
@enduml
单一职责原则
一个类中应该是相关性很强的一类函数.
比如 ImageLoader 提供了图片加载和缓存的功能,但是我们不能将加载和缓存的功能全写到 ImageLoader 类里, 于是提出两个功能类 ImageCache 和 Loader 分别负责缓存和加载功能.
开闭原则
对扩展开放,对修改封闭。实现扩展的方法是使用抽象和依赖注入。将可扩展的功能抽象,然后通过外部注入具体实现.
负责缓存的 ImageCache 和加载的 Loader, 都是可以扩展的功能, 应该让他们开放. ImageLoader 本身的业务一般都不会改变, 应该让它封闭.
所以将缓存和加载功能抽象出来, 形成两个接口, 然后在外边注入具体的实现. 要扩展他们的功能, 就实现他们的抽象接口即可, 然后注入到 ImageLoader 中. 比如缓存可以分内存缓存 MemoryCache 和硬盘缓存 DiskCache.
@startuml
interface ImageCache{
+ Bitmap get(String url)
+ void put(String url, Bitmap b)
}
interface Loader{
+ Bitmap load(String url)
}
ImageCache <|-- MemoryCache
ImageCache <|-- DiskCache
Loader <|-- SyncLoader
Loader <|-- AsyncLoader
@enduml
里氏替换原则
所有父类都能被子类替换,并且不影响程序的正确运行。核心是建立抽象,结合开闭原则,对扩展开放,对修改关闭.
@startuml
interface ImageCache{
+ Bitmap get(String url)
+ void put(String url, Bitmap b)
}
ImageCache <|-- MemoryCache
ImageCache <|-- DiskCache
class ImageLoader{
- ImageCache imageCache
+ void setImageCache(ImageCache cache)
+ void setLoader(Loader loader)
}
ImageLoader -o ImageCache
@enduml
MemoryCache DiskCache 都能替换 ImageCache 的工作, 并且能够保证行为的正确性, ImageCache 建立了获取缓存图片, 储存缓存图片的接口规范, MemoryCache 等根据规范实现了相应的功能, 用户只需在使用时指定具体的缓存对象, 就可以动态替换 ImageLoader 中的缓存策略.
比如 Android SDK 里的 Window 和 View 也是这个原则, Window 中的 setContentView的参数 view 可以被任何一个实现了 View 接口的对象替换.
@startuml
abstract class Window {
+ void setContentView(View view)
}
abstract class View {
+ {abstract} void draw()
+ {abstract} void measure(int int)
}
Window - View
class Button {
+ void draw()
+ void measure(int int)
}
class TextView {
+ void draw()
+ void measure(int int)
}
View <|-- Button
View <|-- TextView
@enduml
依赖倒置原则
高层模块是调用方,底层模块是实现方。高层模块不依赖底层模块的实现细节, 依赖模块被颠倒. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的.
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节:接口或抽象类,不用在意具体的实现
- 细节应该依赖抽象:由实现类去具体实现抽象
例子中的高层模块就是 ImageLoader, 底层模块就是 ImageCache 和 Loader, ImageLoader 依赖的都是 ImageCache 和 Loader 的抽象, 并没有直接依赖某个具体实现.
如果 ImageLoader 直接依赖了 ImageCache 的某一个具体实现(比如 MemoryCache), 当 MemoryCache 不能满足 ImageLoader 而需要被其他缓存实现替换时, 就必须修改 ImageLoader 的代码. 反之, 如果 ImageLoader 依赖的是 ImageCache 抽象, 要替换其他缓存实现时, 只需要外部注入其他实现即可, 不需要修改 ImageLoader的代码.
接口隔离原则
依赖的抽象接口应尽可能的小。比如:实现一个工具类来关闭所有支持
Closeable的对象,该工具类就应该只依赖Closeable这个抽象的接口,而不是FileOutputStream等具体的实现.
@startuml
class CloseUtil{
+ void closeQuietly(Closeable closeable)
}
interface Closeable{
+ void close()
}
abstract class OutputStream
class FileOutputStream
CloseUtil -- Closeable
OutputStream -|> Closeable
FileOutputStream -|> OutputStream
@enduml
迪米特原则
一个对象应该对其他对象有最少的了解,只与直接的朋友通信。尽量保持类的依赖关系是一条没有交叉,没有环的线.