1.设计模式的前奏

news/2024/11/23 3:16:17/

哪些维度评判代码质量的好坏?

常用的评价标准

  • 可维护性(maintainability):维护代码的成本
  • 可读性(readability)
  • 可扩展性(extensibility):码应对未来需求变化的能力
  • 灵活性(flexibility)
  • 简洁性(simplicity)
  • 可复用性(reusability)
  • 可测试性(testability)

面向对象、设计原则、设计模式、编程规范、重构

面向对象

  1. 面向对象的四大特性:封装、抽象、继承、多态
  • 封装:信息隐藏或者数据访问保护
  • 抽象:调用者在使用函数的时候,并不需要去研究函数内部的实现逻辑,只需要通过函数的命名、注释或者文档,了解其提供了什么功能,就可以直接使用了。
  • 继承:代码复用(过度继承会导致代码可读性、可维护性变差)
  • 多态:子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现
  1. 面向对象编程与面向过程编程的区别和联系
  • 面向过程:方法和数据结构的定义分开
  • 面向对象:方法和数据结构被绑定一起,定义在类中(能够应对大规模复杂程序的开发,更易复用、易扩展、易维护)
  1. 接口和抽象类的区别以及各自的应用场景
    抽象类(模板设计模式)
// 抽象类
public abstract class Logger {private String name;private boolean enabled;private Level minPermittedLevel;public Logger(String name, boolean enabled, Level minPermittedLevel) {this.name = name;this.enabled = enabled;this.minPermittedLevel = minPermittedLevel;}public void log(Level level, String message) {boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValueif (!loggable) return;doLog(level, message);}protected abstract void doLog(Level level, String message);
}
// 抽象类的子类:输出日志到文件
public class FileLogger extends Logger {private Writer fileWriter;public FileLogger(String name, boolean enabled,Level minPermittedLevel, String filepath) {super(name, enabled, minPermittedLevel);this.fileWriter = new FileWriter(filepath);}@Overridepublic void doLog(Level level, String mesage) {
// 格式化level和message,输出到日志文件fileWriter.write(...);}
}// 抽象类的子类: 输出日志到消息中间件(比如kafka)
public class MessageQueueLogger extends Logger {private MessageQueueClient msgQueueClient;public MessageQueueLogger(String name, boolean enabled,Level minPermittedLevel, MessageQueueClient msgQueueClient) {super(name, enabled, minPermittedLevel);this.msgQueueClient = msgQueueClient;}@Overrideprotected void doLog(Level level, String mesage) {
// 格式化level和message,输出到消息中间件msgQueueClient.send(...);}
}
  • 抽象类不允许被实例化,只能被继承。
  • 抽象类可以包含属性和方法。
  • 子类继承抽象类,必须实现抽象类中的所有抽象方法。

接口

public interface Filter {void doFilter(RpcRequest req) throws RpcException;
}// 接口实现类:鉴权过滤器
public class AuthencationFilter implements Filter {@Overridepublic void doFilter(RpcRequest req) throws RpcException {
//...鉴权逻辑..}
}// 接口实现类:限流过滤器
public class RateLimitFilter implements Filter {@Overridepublic void doFilter(RpcRequest req) throws RpcException {
//...限流逻辑...}
}// 过滤器使用Demo
public class Application {// filters.add(new AuthencationFilter());
// filters.add(new RateLimitFilter());private List<Filter> filters = new ArrayList<>();public void handleRpcRequest(RpcRequest req) {try {for (Filter filter : filters) {filter.doFilter(req);}} catch (RpcException e) {
// ...处理过滤结果...}
// ...省略其他处理逻辑...}
}
  • 接口不能包含属性(也就是成员变量)。
  • 接口只能声明方法,方法不能包含代码实现。
  • 类实现接口的时候,必须实现接口中声明的所有方法。
  1. 基于接口而非实现编程的设计思想
  • 越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。
    函数的命名不能暴露任何实现细节。
    封装具体的实现细节。
    为实现类定义抽象的接口。
  1. 多用组合少用继承的设计思想
  • 如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关
    系),继承关系不复杂,我们就可以大胆地使用继承。反之,系统越不稳定,继承层次很深,
    继承关系复杂,我们就尽量使用组合来替代继承
  • 装饰者模式(decorator pattern)、策略模式(strategy pattern)、组合模式(composite pattern)等都使用了组合关系,而模板模式(template pattern)使用了继承关系。
  1. 面向过程的贫血模型(重 Service 轻 BO)和面向对象的充血模型(轻 Service 重 Domain)
    基于贫血模型的传统开发模式
// 接口
public class VirtualWalletController {// 通过构造函数或者IOC框架注入private VirtualWalletService virtualWalletService;public BigDecimal getBalance(Long walletId) { ...} //查询余额public void debit(Long walletId, BigDecimal amount) { ...} //出账public void credit(Long walletId, BigDecimal amount) { ...} //入账public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) { .
//省略查询transaction的接口}
}public class VirtualWalletBo {//省略getter/setter/constructor方法private Long id;private Long createTime;private BigDecimal balance;
}public Enum TransactionType {DEBIT,CREDIT,TRANSFER;}public class VirtualWalletService {// 通过构造函数或者IOC框架注入private VirtualWalletRepository walletRepo;private VirtualWalletTransactionRepository transactionRepo;public VirtualWalletBo getVirtualWallet(Long walletId) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWalletBo walletBo = convert(walletEntity);return walletBo;}public BigDecimal getBalance(Long walletId) {return walletRepo.getBalance(walletId);}@Transactionalpublic void debit(Long walletId, BigDecimal amount) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);BigDecimal balance = walletEntity.getBalance();if (balance.compareTo(amount) < 0) {throw new NoSufficientBalanceException(...);}VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactitransactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.DEBIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletId, balance.subtract(amount));}@Transactionalpublic void credit(Long walletId, BigDecimal amount) {VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactitransactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.CREDIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);BigDecimal balance = walletEntity.getBalance();walletRepo.updateBalance(walletId, balance.add(amount));}@Transactionalpublic void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactitransactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.TRANSFER);transactionEntity.setFromWalletId(fromWalletId);transactionEntity.setToWalletId(toWalletId);transactionRepo.saveTransaction(transactionEntity);debit(fromWalletId, amount);credit(toWalletId, amount);}
}

基于充血模型的 DDD 开发模式(钱包系统)

public class VirtualWallet { // Domain领域模型(充血模型)private Long id;private Long createTime = System.currentTimeMillis();private BigDecimal balance = BigDecimal.ZERO;public VirtualWallet(Long preAllocatedId) {this.id = preAllocatedId;}public BigDecimal balance() {return this.balance;}public void debit(BigDecimal amount) {if (this.balance.compareTo(amount) < 0) {throw new InsufficientBalanceException(...);}this.balance = this.balance.subtract(amount);}public void credit(BigDecimal amount) {if (amount.compareTo(BigDecimal.ZERO) < 0) {throw new InvalidAmountException(...);}this.balance = this.balance.add(amount);}
}public class VirtualWalletService {// 通过构造函数或者IOC框架注入private VirtualWalletRepository walletRepo;private VirtualWalletTransactionRepository transactionRepo;public VirtualWallet getVirtualWallet(Long walletId) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWallet wallet = convert(walletEntity);return wallet;}public BigDecimal getBalance(Long walletId) {return walletRepo.getBalance(walletId);}@Transactionalpublic void debit(Long walletId, BigDecimal amount) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWallet wallet = convert(walletEntity);wallet.debit(amount);VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactitransactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.DEBIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletId, wallet.balance());}@Transactionalpublic void credit(Long walletId, BigDecimal amount) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWallet wallet = convert(walletEntity);wallet.credit(amount);VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactitransactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.CREDIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletId, wallet.balance());}@Transactionalpublic void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
//...跟基于贫血模型的传统开发模式的代码一样...}
}

基于充血模型的 DDD 开发模式跟基于贫血模型的传统开发模式相比,主要区别在 Service
层。在基于充血模型的开发模式下,我们将部分原来在 Service 类中的业务逻辑移动到了一个
充血的 Domain 领域模型中
,让 Service 类的实现依赖这个 Domain 类。

在基于充血模型的 DDD 开发模式下,Service 类并不会完全移除,而是负责一些不适合放在
Domain 类中的功能。比如,负责与 Repository 层打交道、跨领域模型的业务聚合功能、幂
等事务等非功能性的工作。

如何对接口鉴权这样一个功能开发做面向对象分析?

  1. 调用方进行接口请求的时候,将 URL、AppID、密码、时间戳拼接在一起,通过加密算法
    生成 token,并且将 token、AppID、时间戳拼接在 URL 中,一并发送到微服务端。
  2. 微服务端在接收到调用方的接口请求之后,从请求中拆解出 token、AppID、时间戳。
  3. 微服务端首先检查传递过来的时间戳跟当前时间,是否在 token 失效时间窗口内。如果已
    经超过失效时间,那就算接口调用鉴权失败,拒绝接口调用请求。
  4. 如果 token 验证没有过期失效,微服务端再从自己的存储中,取出 AppID 对应的密码,通
    过同样的 token 生成算法,生成另外一个 token,与调用方传递过来的 token 进行匹配。
    如果一致,则鉴权成功,允许接口调用;否则就拒绝接口调用。

进一步变化为:

  1. 把 URL、AppID、密码、时间戳拼接为一个字符串;
  2. 对字符串通过加密算法加密生成 token;
  3. 将 token、AppID、时间戳拼接到 URL 中,形成新的 URL;
  4. 解析 URL,得到 token、AppID、时间戳等信息;
  5. 从存储中取出 AppID 和对应的密码;
  6. 根据时间戳判断 token 是否过期失效;
  7. 验证两个 token 是否匹配;

1、2、6、7 都是跟 token 有关,负责 token 的生成、验证;(AuthToken)
3、4 都是在处理 URL,负责 URL 的拼接、解析;(Url)
5 是操作 AppID 和密码,负责从存储中读取 AppID 和密码。(CredentialStorage)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public interface ApiAuthenticator {void auth(String url);void auth(ApiRequest apiRequest);
}
public class DefaultApiAuthenticatorImpl implements ApiAuthenticator {private CredentialStorage credentialStorage;public DefaultApiAuthenticatorImpl() {this.credentialStorage = new MysqlCredentialStorage();}public DefaultApiAuthenticatorImpl(CredentialStorage credentialStorage) {this.credentialStorage = credentialStorage;}@Overridepublic void auth(String url) {ApiRequest apiRequest = ApiRequest.buildFromUrl(url);auth(apiRequest);}@Overridepublic void auth(ApiRequest apiRequest) {String appId = apiRequest.getAppId();String token = apiRequest.getToken();long timestamp = apiRequest.getTimestamp();String originalUrl = apiRequest.getOriginalUrl();AuthToken clientAuthToken = new AuthToken(token, timestamp);if (clientAuthToken.isExpired()) {throw new RuntimeException("Token is expired.");}String password = credentialStorage.getPasswordByAppId(appId);AuthToken serverAuthToken = AuthToken.generate(originalUrl, appId, password,if (!serverAuthToken.match(clientAuthToken)) {throw new RuntimeException("Token verfication failed.");}}
}

设计原则

  1. SOLID 原则 -SRP 单一职责原则
  2. SOLID 原则 -OCP 开闭原则(对扩展开放、对修改关闭)
// API 接口监控告警 这种情况进行扩展就需要对方法进行修改
public class Alert {private AlertRule rule;private Notification notification;public Alert(AlertRule rule, Notification notification) {this.rule = rule;this.notification = notification;}public void check(String api, long requestCount, long errorCount, long duration) {long tps = requestCount / durationOfSeconds;if (tps > rule.getMatchedRule(api).getMaxTps()) {notification.notify(NotificationEmergencyLevel.URGENCY, "...");}if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {notification.notify(NotificationEmergencyLevel.SEVERE, "...");}}
}

// 当每秒钟接口超时请求个数,超过某个预先设置的最大阈值时,我们也要触发告警发送通知。
代码改动


public class Alert {// ...省略AlertRule/Notification属性和构造函数...
// 改动一:添加参数timeoutCountpublic void check(String api, long requestCount, long errorCount, long timeoutCount) {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, "...");}}
}

重构–》

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);}}
}public class ApiStatInfo {//省略constructor/getter/setter方法private String api;private long requestCount;private long errorCount;private long durationOfSeconds;
}public abstract class AlertHandler {protected AlertRule rule;protected Notification notification;public AlertHandler(AlertRule rule, Notification notification) {this.rule = rule;this.notification = notification;}public abstract void check(ApiStatInfo apiStatInfo);
}public class TpsAlertHandler extends AlertHandler {public TpsAlertHandler(AlertRule rule, Notification notification) {super(rule, notification);}@Overridepublic void check(ApiStatInfo apiStatInfo) {long tps = apiStatInfo.getRequestCount() / apiStatInfo.getDurationOfSeconds();if (tps > rule.getMatchedRule(apiStatInfo.getApi()).getMaxTps()) {notification.notify(NotificationEmergencyLevel.URGENCY, "...");}}
}public class ErrorAlertHandler extends AlertHandler {public ErrorAlertHandler(AlertRule rule, Notification notification) {super(rule, notification);}@Overridepublic void check(ApiStatInfo apiStatInfo) {if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi()).gnotification.notify(NotificationEmergencyLevel.SEVERE, "...");}
}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);}
}
  1. SOLID 原则 -LSP 里式替换原则
  • 子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性(继承父类初衷,并进行增强)
  1. SOLID 原则 -ISP 接口隔离原则
  • 要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数
  • 单一职责原则针对的是模块、类、接口的设计。接口隔离原则更针对接口的设计。
  1. SOLID 原则 -DIP 依赖倒置原则
  • 控制反转(IOC):框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。
  • 依赖注入(DI):不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
  • 依赖反转: 高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
  1. DRY 原则、KISS 原则、YAGNI 原则、LOD 法则
  • KISS 原则(Keep It Simple and Stupid:尽量保持简单)
    • 不要使用同事可能不懂的技术来实现代码。
    • 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出bug 的概率会更高,维护的成本也比较高。
    • 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。
  • YAGNI(You Ain’t Gonna Need It:你不会需要它):
    • 不要去设计当前用不到的功能;不要去编写当前用不到的代码。
  • DRY原则(Don’t Repeat Yourself:(不写重复的代码))
    • 三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。
      提高复用的办法
    • 减少代码耦合
    • 满足单一职责原则
    • 模块化
    • 业务与非业务逻辑分离
    • 通用代码下沉
    • 继承、多态、抽象、封装
    • 应用模板等设计模式
  • LOD(迪米特法则:Law of Demeter):不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口

设计模式

针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思
路。

积分系统设计案例

  1. 合理地将功能划分到不同模块
    • 第一种划分方式是:积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护(增删改查),不划分到积分系统中,而是放到更上层的营销系统中。这样积分系统就会变得非常简单,只需要负责增加积分、减少积分、查询积分、查询积分明细等这几个工作
    • 第二种划分方式是:积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护,分散在各个相关业务系统中,比如订单系统、评论系统、签到系统、换购商城、优惠券系统等。还是刚刚那个下订单赚取积分的例子,在这种情况下,用户下订单成功之后,订单系统根据商品对应的积分兑换比例,计算所能兑换的积分数量,然后直接调用积分系统给用户增加积分。
    • 第三种划分方式是:所有的功能都划分到积分系统中,包括积分赚取渠道及兑换规则、消费渠道及兑换规则的管理和维护。还是同样的例子,用户下订单成功之后,订单系统直接告知积分系统订单交易成功,积分系统根据订单信息查询积分兑换规则,给用户增加积分。

综合考虑,我们更倾向于第一种和第二种模块划分方式。但是,不管选择这两种中的哪一种,积分系统所负责的工作是一样的,只包含积分的增、减、查询,以及积分明细的记录和查询。

  1. 设计模块与模块之间的交互关系
    上下层系统之间的调用倾向于通过同步接口,同层之间的调用倾向于异步消息调用。比如,营销系统和积分系统是上下层关系,它们之间就比较推荐使用同步接口调用(上层是调用系统,下层是被调用系统)

  2. 设计模块的接口、数据库、业务模型
    积分流水明细表
    在这里插入图片描述
    表设计这里还可以加一个积分余额表(没有积分余额表容易造成多扣)

积分系统的接口
在这里插入图片描述

编程规范

编程规范主要解决的是代码的可读性问题。编码规范相对于设计原则、设计模式,更加具
体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础主要就是编程规范。

重构

重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编
码规范这些理论。

程序出错该返回啥?NULL、异常、错误码、空对象?

  1. 返回错误码
    • C 语言使用错误码的情况比较多
  2. 返回 NULL 值
    • 如果查询对象为空时,返回null也是正常行为(按照项目统一约定来),比如查询下标返回值会是Int类型,这时候可能用-1来返回
  3. 返回空对象
    • 空对象设计模式(Null Object Design Pattern)
    • 当函数返回的数据是字符串类型或者集合类型的时候,我们可以用空字符串或空集合替代NULL 值,来表示不存在的情况。
  4. 抛出异常对象
    • 对于代码 bug(比如数组越界)以及不可恢复异常(比如数据库连接失败),即便我们捕获了,也做不了太多事情,所以,我们倾向于使用非受检异常。对于可恢复异常、业务异常,比如提现金额大于余额的异常,我们更倾向于使用受检异常,明确告知调用者需要捕获处理。

当我们面对函数抛出异常的时候,应该选择上面的哪种处理方式呢?

  • 如果 func1() 抛出的异常是可以恢复,且 func2() 的调用方并不关心此异常,我们完全可以在 func2() 内将 func1() 抛出的异常吞掉;
  • 如果 func1() 抛出的异常对 func2() 的调用方来说,也是可以理解的、关心的 ,并且在业务概念上有一定的相关性,我们可以选择直接将 func1 抛出的异常 re-throw;
  • 如果 func1() 抛出的异常太底层,对 func2() 的调用方来说,缺乏背景去理解、且业务概念上无关,我们可以将它重新包装成调用方可以理解的新异常,然后 re-throw。

http://www.ppmy.cn/news/19942.html

相关文章

11.Java方法的综合练习题大全-双色球彩票系统,数字的加密和解密等试题

本篇文章是Java方法的专题练习,从第五题开始难度增大,涉及大厂真题,前四道题目是基础练习,友友们可有目的性的选择学习&#x1f618;&#x1f495; 文章目录前言一、数组的遍历1.注意点:输出语句的用法2.题目正解二、数组最大值三、判断是否存在四、复制数组五、案例一:卖飞机票…

XC-16 SpringSecurity Oauth2 JWT

SpringSecurityOauth2用户认证需求分析用户认证与授权单点登录需求第三方认证需求用户认证技术方案单点登录技术方案Oauth2认证Oauth2认证流程2.2.2Oauth2在本项目中的应用SpringSecurity Oauth2认证解决方案SpringSecurityOauth2研目标搭建认证服务器导入基础工程创建数据库Oa…

万字长文--详解Node.js(快速入门)

Node.js基础与扩展Node.js1、初识Node.js与内置模块1.1 Node.js初识1.2 fs文件系统模块1.3 path路径模块1.4 http模块2、模块化2.1 模块化的基本概念2.2 Node.js中模块化2.3 npm与包2.4 模块的加载机制3、Express3.1 初识Express3.2 Express路由3.3 Express中间件3.4 使用Expre…

python图像处理(图像缩放)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 图像缩放也是isp处理的一个基本功能。现在的camera像素越来越大,但是显示设备的分辨率是一定的,如果想把图像显示在显示器或者lcd上面,那就要符合对应显示设备的分辨率。一般来说…

C++11中的完美转发

C11中的完美转发 在讨论引用折叠这个话题之前&#xff0c;先回顾一下C11中的引用&#xff0c; 在C11中引用有4种&#xff1a;非常量左值引用、非常量右值引用、常量左值引用、常量右值引用。其中常量右值引用没有应用价值,所以我们不考虑。 非常量左值引用只能绑定非常量左值…

编译原理学习笔记14——属性文法与语法制导翻译1

编译原理学习笔记14——属性文法与语法制导翻译114.1 属性文法14.2 属性计算14.1 属性文法 属性文法 综合属性 自下而上传递信息语法规则&#xff1a;根据右 部候选式中的符号 的属性计算左部被 定义符号的综合属性语法树&#xff1a;根据子结 点的属性和父结点 自身的属性…

《C++程序设计原理与实践》笔记 第11章 定制输入/输出

在本章中&#xff0c;我们重点关注如何使第10章中介绍的通用iostream框架适配特定的需求和偏好。 11.1 规则性和不规则性 C标准库的输入/输出部分——iostream库为文本的输入和输出提供了一个统一的、可扩展的框架。 到目前为止&#xff0c;我们将所有输入源视为等价的&…

网络原理之HTTP/HTTPS、TCP、IP四层协议栈

文章目录一、应用层&#xff08;一&#xff09;xml协议&#xff08;二&#xff09;json协议&#xff08;三&#xff09;protobuffer协议&#xff08;四&#xff09;HTTP协议1. 抓包工具&#xff0c;fiddler2. HTTP报文格式3. HTTP请求(Request)&#xff08;1&#xff09;URL基本…