←←←←←←←←←←←← 快!点关注
SOLID阐述了五种设计原则,可帮助开发人员轻松扩展和维护软件:
S - 单一责任原则
O - 开放原则
L - Liskov替代原理
I - 界面隔离原理
D - 依赖倒置原则
单一责任原则
一个类应该有一个,而且只有一个改变的理由。
一个类应该只有一个责任,这意味着类应该高度凝聚并实现强相关的逻辑。实现功能1和功能2和功能3(依此类推)的类违反了SRP。
SRP示例:
// BAD
public class UserSettingService {
public void changeEmail(User user) {
if (checkAccess(user)) {
//Grant option to change
}
}
public boolean checkAccess(User user) {
//Verify if the user is valid.
}
}
// GOOD
public class UserSettingService {
public void changeEmail(User user) {
if (securityService.checkAccess(user)) {
//Grant option to change
}
}
}
public class SecurityService {
public static boolean checkAccess(User user) {
//check the access.
}
}
SRP味道
- 单个类中不止一个上下文分隔的代码
- 测试中的大型setup初始化设置(TDD在检测SRP违规时非常有用)
SRP好处
- 负责给定用例的分隔类现在可以在应用程序的其他部分中重用
- 负责给定用例的分隔类现在可以单独测试
开/闭原则
您应该能够扩展类行为,而无需对其进行修改。
类应该打开以进行扩展并关闭以进行修改。您应该能够扩展类行为而无需修改其实现:
// BAD
public class Logger {
String logging;
public Logger(String logging) {
this.logging = logging;
}
public void log() {
if ("console".equals(logging)) {
// Log to console
} else if ("file".equals(logging)) {
// Log to file
}
}
}
// GOOD
public interface Log {
void log();
}
public class ConsoleLog implements Log {
void log() {
// Log to console
}
}
public class FileLog implements Log {
void log() {
// Log to file
}
}
public class Logger {
Log log;
public Logger(Log log) {
this.log = log;
}
public void log() {
this.log.log();
}
}
OCP代码味道:
- 如果你注意到类X直接引用其代码库中的其他类Y,则表明类Y应该传递给类X(通过构造函数/单个方法),例如通过依赖注入
- 复杂的if-else或switch语句
OCP好处:
- 使用封装在单独类中的新功能可以轻松扩展X类功能,而无需更改类X实现(它不知道引入的更改)
- 代码松散耦合
- 注入的Y类可以在测试中轻易模拟
利斯科夫替代原则
派生类必须可替代其基类。
这是开/闭原则的延伸。派生类不应更改基类的行为(继承方法的行为)。如果类Y是类X的子类,则任何引用类X的实例也应该能够引用类Y(派生类型必须完全替代它们的基类型)。
// BAD
public class DataHashSet extends HashSet {
int addCount = 0;
public boolean function add(Object object) {
addCount++;
return super.add(object);
}
// the size of count will be added twice!
public boolean function addAll(Collection collection) {
addCount += collection.size();
return super.addAll(collection);
}
}
// GOOD: Delegation Over Subtyping
public class DataHashSet implements Set {
int addCount = 0;
Set set;
public DataHashSet(Set set) {
this.set = set;
}
public boolean add(Object object) {
addCount++;
return this.set.add(object);
}
public boolean addAll(Collection collection) {
addCount += collection.size();
return this.set.addAll(collection);
}
}
LSP代码味道:
- 如果它看起来像一只鸭子,嘎嘎叫像鸭子但需要电池才能达到这个目的 - 这可能违反了LSP
- 修改子类中的继承行为
- 在重写的继承方法中引发的异常
LSP好处:
- 避免意外和不正确的结果
- 明确区分共享继承接口和扩展功能
接口隔离原理
制作客户端特定的细粒度接口。
一旦接口变得太大/太胖,我们绝对需要将其拆分为更具体的小接口。接口将由将使用它的客户端定义,这意味着接口的客户端将只知道与它们相关的方法。
// BAD
public interface Car {
Status open();
Speed drive(Gas gas);
Engine changeEngine(Engine newEngine);
}
public class Driver {
public Driver(Car car) {}
public Speed ride() {
this.car.open();
return this.car.drive(new Gas(10));
}
}
public class Mechanic {
public Mechanic(Car car) {}
public Engine fixEngine(Engine newEngine) {
return this.car.changeEngine(newEngine);
}
}
// GOOD
public interface RidableCar {
Status open();
Speed drive(Gas gas);
}
public interface FixableCar {
Engine changeEngine(Engine newEngine);
}
public class Driver {
// Same with RidableCar
}
public class Mechanic {
// Same with FixableCar
}
ISP代码味道
- 由许多类实现的一个胖接口,其中没有一个类实现100%的接口方法。这种胖接口应该分成适合客户需求的较小接口
ISP好处
- 高度凝聚力的代码
- 避免使用单个胖接口在所有类之间进行耦合(一旦单个胖接口中的方法得到更新,所有类 - 无论是否使用此方法 - 都被迫相应地更新)
- 通过将职责分组到单独的界面中,明确分离业务逻辑
依赖倒置原则
依赖于抽象,而不是实现
如果您的实现细节将取决于更高级别的抽象,它将帮助您获得正确耦合的系统。而且,它将影响该系统的封装和内聚。
// BAD
public class SQLDatabase {
public void connect() {
String connectionstring = System.getProperty("MSSQLConnection");
// Make DB Connection
}
public Object search(String key) {
// Do SQL Statement
return query.find();
}
}
public class DocumentDatabase {
// Same logic but with document details
}
// GOOD
public interface Connector {
Connection open();
}
public interface Finder {
Object find(String key);
}
public class MySqlConnector implements Connector {}
public class DocumentConnector implements Connector {}
public class MySqlFinder implements Finder {}
public class DocumentFinder implements Finder {}
public class Database {
public Database(Connector connector,
Finder finder) {
this.connector = connector;
this.finder = finder;
}
public Connection connect() {
return connector.open();
}
public Object search(String key) {
return finder.find(key);
}
}
DIP味道:
- 在高级模块中实例化低级模块
- 调用低级模块/类的类方法
DIP好处:
- 通过使更高级别的模块独立于较低级别的模块来提高其可重用性
- 可以采用依赖性注入1来促进所选择的低级组件实现的运行时供应到高级组件
- 注入类可以在测试中轻易模拟
写在最后:
秃顶程序员的不易,看到这里,点了关注吧!
点关注,不迷路,持续更新!!!
网友评论