首页 > 其他分享 >面向对象设计原则

面向对象设计原则

时间:2024-01-24 21:46:20浏览次数:21  
标签:... return String 原则 private 面向对象 new 设计 public

一、面向对象

#OOP实际包含OOA(面向对象分析),OOD(面向对象设计),OOP(面向对象编程)三部分

#封装:隐藏信息、保护数据;属性暴露get方法,修改通过方法定义暴露 get;private set;
#抽象:隐藏方法的具体实现,调用者只需要关心方法提供了哪些功能
#继承:代码复用;两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。

#Constants,ErrorCode,Utils建议:建立文件夹,根据不同业务创建子类

#面向对象思考方式:
面向过程:自底向上,第1步做什么,第2步做什么
面向对象:自上到下,分析需要哪些类,以及类之间如何交互

#组合优先于继承:组合、接口、委托

public interface Flyable {
void fly();
}

public class FlyAbility implements Flyable {
@Override
public void fly() { //... }
}

//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鸵鸟
private TweetAbility tweetAbility = new TweetAbility(); //组合
private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
//... 省略其他属性和方法...
@Override
public void tweet() {
tweetAbility.tweet(); // 委托
}
@Override
public void layEgg() {
eggLayAbility.layEgg(); // 委托
}
}

#现有代码结构
数据访问层:UserEntity 和 UserRepository
业务逻辑层:UserBo 和 UserService 组 XxxFactory:负责Entity和BO转换
接口访问层:UserVo 和 UserController XxxAssembler: 负责Vo和BO转换

*** 领域驱动模式优化=> Service层包含Service和Domain层两部分

#贫血模式:重Service,轻Bo
#充血模式:轻Service,重Domain

#SQL驱动开发:定义Entity,BO,VO(DTO)对象,实现Factroy,Assembler转换类,实现Repository,Service,Controler层 =》引申代码自动生成工具

二、设计原则

#单一职责原则SRP
1个类只负责完成一个职责或者功能。也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。
换个角度来讲就是,一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。

反例:一个类包含订单和用户的操作
判断类是否单一:
public class UserInfo {
private long userId;
private String username;
private String email;
private String telephone;
private long createTime;
private long lastLoginTime;
private String avatarUrl;
private String provinceOfAddress; // 省
private String cityOfAddress; // 市
private String regionOfAddress; // 区
private String detailedAddress; // 详细地址
// ...省略其他属性和方法...
}
>>> 基于业务发展考虑: 地址信息、联系方式可以封装为值对象

 

#开闭原则:对扩展开放,对修改封闭
添加一个新的功能应该是:在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。

public class Alert {
// ...省略AlertRule/Notification属性和构造函数...


public void check(String api, long requestCount, long errorCount, long timeoutCount, long durationOfSeconds) {

long tps = requestCount / durationOfSeconds;
if (tps > rule.getMatchedRule(api).getMaxTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "...");
}

if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {
notification.notify(NotificationEmergencyLevel.SEVERE, "...");
}

// 改动二:添加接口超时处理逻辑
long timeoutTps = timeoutCount / durationOfSeconds;
if (timeoutTps > rule.getMatchedRule(api).getMaxTimeoutTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "...");
}
}
}

暴露问题
1、入参函数过长
2、if连条过长

优化措施
1、封装入参
2、实现抽象类Handler
3、基于组合列表模式,处理各种Hanlder

public class Alert {
private List<AlertHandler> alertHandlers = new ArrayList<>();

public void addAlertHandler(AlertHandler alertHandler) {
this.alertHandlers.add(alertHandler);
}

public void check(ApiStatInfo apiStatInfo) {
for (AlertHandler handler : alertHandlers) {
handler.check(apiStatInfo);
}
}
}

//xxContext初始化抽象类过程,单例模式
public class ApplicationContext {
private AlertRule alertRule;
private Notification notification;
private Alert alert;

public void initializeBeans() {
alertRule = new AlertRule(/*.省略参数.*/); //省略一些初始化代码
notification = new Notification(/*.省略参数.*/); //省略一些初始化代码
alert = new Alert();
alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));
alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));
}
public Alert getAlert() { return alert; }

// 饿汉式单例
private static final ApplicationContext instance = new ApplicationContext();
private ApplicationContext() {
initializeBeans();
}
public static ApplicationContext getInstance() {
return instance;
}
}

public class Demo {
public static void main(String[] args) {
ApiStatInfo apiStatInfo = new ApiStatInfo();
// ...省略设置apiStatInfo数据值的代码
ApplicationContext.getInstance().getAlert().check(apiStatInfo);
}
}

#里式替换原则 LSP  子类可以替换父类功能
public class Transporter {
public class SecurityTransporter extends Transporter {

public class Demo {
public void demoFunction(Transporter transporter) {

Demo demo = new Demo();
demo.demofunction(new SecurityTransporter(/*省略参数*/););

#接口隔离原则:客户端不应该被强迫依赖它不需要的接口

public interface UserService {
boolean register(String cellphone, String password);
boolean login(String cellphone, String password);
UserInfo getUserInfoById(long id);
UserInfo getUserInfoByCellphone(String cellphone);
}

//这里删除权限属于高微操作,避免直接暴露给客户端,防止调用
public interface RestrictedUserService {
boolean deleteUserByCellphone(String cellphone);
boolean deleteUserById(long id);
}

public class UserServiceImpl implements UserService, RestrictedUserService {
// ...省略实现代码...
}


KISS: 如何做(保持简单,保持开发都可以阅读)

YAGNI: 要做不要的问题(当前不需要的就不做,不要过度设计)

DYI:不要重复自己,不写重复的代码

迪米特法则:最小知识原则,实现代码的高内聚和低耦合

#1 高内聚:类本身的设计 松耦合:类之间依赖的设计


public class NetworkTransporter {
// 省略属性和其他方法...
public Byte[] send(HtmlRequest htmlRequest) {
//...
}
}

问题:底层通讯类,功能通用,不只局限于Http请求

public class NetworkTransporter {
// 省略属性和其他方法...
public Byte[] send(String address, Byte[] data) { //钱包&钱典故,不应该给钱包,让服务员从钱包取钱
//...
}
}


public class HtmlDownloader {
private NetworkTransporter transporter;//通过构造函数或IOC注入

public Html downloadHtml(String url) {
Byte[] rawHtml = transporter.send(new HtmlRequest(url));
return new Html(rawHtml);
}
}

 


public class Document {
private Html html;
private String url;

public Document(String url) {
this.url = url;
HtmlDownloader downloader = new HtmlDownloader();
this.html = downloader.downloadHtml(url);
}
//...
}

#HtmlDownloader 对象在构造函数中通过 new 来创建,违反了基于接口而非实现编程的设计思想,也会影响到代码的可测试性。
#Document不依赖于HtmlDownloader

public class Document {
private Html html;
private String url;

public Document(String url, Html html) {
this.html = html;
this.url = url;
}
//...
}

// 通过一个工厂方法来创建Document
public class DocumentFactory {
private HtmlDownloader downloader;

public DocumentFactory(HtmlDownloader downloader) {
this.downloader = downloader;
}

public Document createDocument(String url) {
Html html = downloader.downloadHtml(url);
return new Document(url, html);
}
}

#需求分析:线框图和用例图
#系统设计
1、划分不同的模块:不同功能归类到不同模块框
2、设计模块的交互:同步接口调用&异步MQ消息
3、模块设计:模块接口、数据库、业务模型

反思:
1、尽调:尽调需求、调单管理、触达管理、提交、审核、资料留存、配置
2、消息划分:异步虚线
3、模块设计:接口、数据库、业务模型
4、流程设计:前置约束检验和后置结果检查、异常处理机制(重试或补偿)

#接口设计

序号 接口 参数 返回
1 查询接口 xx xx


三、规范和重构

互联网产品特点:快、糙、猛

#持续重构意识&单元测试
单元测试要求:覆盖正常和异常情况的场景

public boolean execute() throws InvalidTransactionException {
if ((buyerId == null || (sellerId == null || amount < 0.0) {
throw new InvalidTransactionException(...);
}
if (status == STATUS.EXECUTED) return true; //***第一层check
boolean isLocked = false;
try {
isLocked = RedisDistributedLock.getSingletonIntance().lockTransction(id); //***获取分布式锁
if (!isLocked) {
return false; // 锁定未成功,返回false,job兜底执行
}
if (status == STATUS.EXECUTED) return true; //*** double check

long executionInvokedTimestamp = System.currentTimestamp();
if (executionInvokedTimestamp - createdTimestap > 14days) {
this.status = STATUS.EXPIRED;
return false;
}
WalletRpcService walletRpcService = new WalletRpcService();
String walletTransactionId = walletRpcService.moveMoney(id, buyerId, sellerId, amount);
if (walletTransactionId != null) {
this.walletTransactionId = walletTransactionId;
this.status = STATUS.EXECUTED;
return true;
} else {
this.status = STATUS.FAILED;
return false;
}
} finally {
if (isLocked) { //***解除锁
RedisDistributedLock.getSingletonIntance().unlockTransction(id);
}
}
}
}

问题:依赖于分布式锁和walletRpcService,测试搭建成本太高
解决方式:
1、构造函数依赖注入
2、框架支持获取Mock实例,提供注册Mock类型缓存方法,当获取实例时,如果Mock Cache实例在,则优先获取Mock实例
不推荐方法依赖于抽象部分,导致参数过长,另外这种属于不变的部分

public class Transaction {
//...
// 添加一个成员变量及其set方法
private WalletRpcService walletRpcService;

public void setWalletRpcService(WalletRpcService walletRpcService) {
this.walletRpcService = walletRpcService;
}
}

//WalletRpcService提供虚拟方法
public class MockWalletRpcServiceOne extends WalletRpcService {
public String moveMoney(Long id, Long fromUserId, Long toUserId, Double amount) {
return "123bac";
}
}

public class MockWalletRpcServiceTwo extends WalletRpcService {
public String moveMoney(Long id, Long fromUserId, Long toUserId, Double amount) {
return null;
}
}


-------------------------------------------------------
通过依赖注入解除测试Mock问题

public void testExecute() {
Long buyerId = 123L;
Long sellerId = 234L;
Long productId = 345L;
Long orderId = 456L;

TransactionLock mockLock = new TransactionLock() {
public boolean lock(String id) {
return true;
}

public void unlock() {}
};

Transction transaction = new Transaction(null, buyerId, sellerId, productId, orderId);
transaction.setWalletRpcService(new MockWalletRpcServiceOne()); //mock1
transaction.setTransactionLock(mockLock); //mock2
boolean executedResult = transaction.execute();
assertTrue(executedResult);
assertEquals(STATUS.EXECUTED, transaction.getStatus());
}

//过期时间不好测试
public class Transaction {
protected boolean isExpired() {
long executionInvokedTimestamp = System.currentTimestamp();
return executionInvokedTimestamp - createdTimestamp > 14days;
}
}

#消除null逻辑判断
public List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {
if (startDate != null && endDate != null) {
// 查询两个时间区间的transactions
}
if (startDate != null && endDate == null) {
// 查询startDate之后的所有transactions
}
if (startDate == null && endDate != null) {
// 查询endDate之前的所有transactions
}
if (startDate == null && endDate == null) {
// 查询所有的transactions
}
}

// 拆分成多个public函数,更加清晰、易用
public List<Transaction> selectTransactionsBetween(Long userId, Date startDate, Date endDate) {
return selectTransactions(userId, startDate, endDate);
}

public List<Transaction> selectTransactionsStartWith(Long userId, Date startDate) {
return selectTransactions(userId, startDate, null);
}

public List<Transaction> selectTransactionsEndWith(Long userId, Date endDate) {
return selectTransactions(userId, null, endDate);
}

public List<Transaction> selectAllTransactions(Long userId) {
return selectTransactions(userId, null, null);
}

private List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {
// ...
}

#函数设计尽量单一
public boolean checkUserIfExisting(String telephone, String username, String email) {
if (!StringUtils.isBlank(telephone)) {
User user = userRepo.selectUserByTelephone(telephone);
return user != null;
}

if (!StringUtils.isBlank(username)) {
User user = userRepo.selectUserByUsername(username);
return user != null;
}

if (!StringUtils.isBlank(email)) {
User user = userRepo.selectUserByEmail(email);
return user != null;
}

return false;
}

// 拆分成三个函数
public boolean checkUserIfExistingByTelephone(String telephone);
public boolean checkUserIfExistingByUsername(String username);
public boolean checkUserIfExistingByEmail(String email);

 

标签:...,return,String,原则,private,面向对象,new,设计,public
From: https://www.cnblogs.com/binfirechen/p/17985913

相关文章