傻叉的视频讲的垃圾,英语也被吐槽
水视频,哎,
设计原则
在软件设计中,设计原则是指导开发人员和架构师如何构建高效、可维护、可扩展系统的一些基本准则。遵循设计原则有助于增强代码的清晰度、可重用性、可测试性,并且避免复杂和不必要的设计负担。以下是一些重要的设计原则:
1. 单一职责原则(SRP)
- 定义:一个类应该只有一个职责,且该职责应该是类的唯一原因。
- 目的:减少类的复杂度,确保每个类只关心一种功能,从而提高类的可理解性和可维护性。
- 应用:如果一个类承担了多个职责,它可能会变得很复杂,修改一个职责时可能影响到其他职责,因此要尽量避免。
例子:假设一个“报告生成器”类,它既负责报告的生成,也负责报告的保存。这时候应该考虑将“报告生成”和“报告保存”分开成不同的类。
2. 开放封闭原则(OCP)
- 定义:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
- 目的:增强系统的可扩展性,同时又不破坏现有代码。
- 应用:当需求变化时,尽量通过扩展现有类的功能来实现,而不是直接修改原有代码。例如使用继承、接口等机制。
例子:一个支付系统,随着业务需求的增加,可能会增加新的支付方式。我们可以通过扩展支付接口,而不是修改现有支付类来支持新的支付方式。
3. 里氏替换原则(LSP)
- 定义:子类型对象应该能够替换父类型对象,并且程序的行为不变。
- 目的:确保继承的正确性,子类能够在父类的范围内正常工作,并且不改变系统的行为。
- 应用:子类应当遵循父类的行为约定,不引入异常的行为,确保代码的可替换性。
例子:如果一个类 Bird 有一个 fly 方法,那么所有继承 Bird 的类(如 Sparrow)应该都能执行 fly 方法。如果我们引入一个 Penguin 类,它不能飞,那么 Penguin 应该不继承 Bird,而是另设计一个类。
4. 依赖倒置原则(DIP)
- 定义:高层模块不应依赖于低层模块,二者都应依赖于抽象;抽象不应依赖于细节,细节应依赖于抽象。
- 目的:减少模块之间的耦合度,增强系统的灵活性和可维护性。
- 应用:避免直接依赖具体实现,通过接口或抽象类将高层模块与低层模块解耦。
例子:假设一个 DatabaseService 类需要读取数据,而它直接依赖于 MySQLDatabase 类。为了遵循 DIP,我们可以定义一个 Database 接口,高层模块(如 DatabaseService)依赖于 Database 接口,而不是具体实现 MySQLDatabase。
5. 接口隔离原则(ISP)
- 定义:不应强迫客户端依赖它们不使用的方法。
- 目的:通过拆分大接口,避免不必要的依赖,提高系统的灵活性。
- 应用:接口应该尽可能小且专注于某一功能,避免让实现类承担不需要的职责。
例子:假设有一个 Machine 接口,包含 print、scan 和 fax 三个方法,如果某个类只需要 print 方法,而不是 scan 和 fax,那么它不应依赖一个包含所有这些方法的大接口。此时,可以将接口拆分成多个小接口,如 Printer、Scanner、Fax。
6. 最小知识原则(Law of Demeter)
- 定义:一个对象应该对其他对象有尽可能少的了解,只与直接的朋友交互。
- 目的:减少模块之间的耦合,提高系统的可维护性。
- 应用:每个模块或类的设计都应该尽量减少对其他类的依赖,尤其是避免直接访问其他对象的内部数据。
例子:在一个购物车的实现中,如果 ShoppingCart 对象直接访问了 Product 类内部的价格属性,那么就违反了最小知识原则。正确的做法是通过提供合适的接口让 ShoppingCart 与 Product 交互。
7. 组合/聚合优于继承原则
- 定义:优先使用对象的组合和聚合,而不是继承来实现系统功能。
- 目的:通过组合和聚合可以增强系统的灵活性,避免继承带来的紧耦合问题。
- 应用:在设计系统时,考虑通过组合来组合不同的行为,而不是通过继承来扩展功能。
例子:如果一个 Car 类继承自 Vehicle,而 Truck 也继承自 Vehicle,但是它们有不同的行为,可能会出现多个类层级。更好的方法是将 Engine、Wheel 等组件组合到 Car 类中,而不是通过继承来扩展不同类型的车。
8. 最优解与折中
- 定义:在解决问题时,往往会在不同的设计方案之间做折中,选择一个“最优”的解。
- 目的:在性能、可维护性、可扩展性、成本等方面达到平衡。
- 应用:在设计过程中,往往需要考虑实际的项目需求,选择合适的技术栈和设计方案,而非追求“完美”的解决方案。
例子:在设计数据库模型时,可能需要在规范化和性能之间做权衡,选择适合实际需求的数据库架构。
9. 避免重复代码(DRY原则)
- 定义:Don't Repeat Yourself,避免重复的代码和逻辑。
- 目的:提高代码的可维护性和重用性,减少潜在的错误。
- 应用:当代码中有重复逻辑时,应该提取成通用的函数或模块,而不是在多个地方复制粘贴。
例子:如果多个方法中都有类似的验证逻辑,可以将这些逻辑提取成一个单独的验证函数,避免在多个地方重复代码。
10. KISS原则
- 定义:Keep It Simple, Stupid,保持简单,不要过度设计。
- 目的:避免复杂化设计,追求最简洁、最易理解的解决方案。
- 应用:解决问题时,尽量选择简单、直接的方式,不要使用复杂的技术或方法,避免引入不必要的复杂性。
例子:在开发一个用户登录系统时,使用简单的用户名和密码验证方式,而不是一开始就加入复杂的两因素认证,除非有明确的安全需求。
总结
这些设计原则提供了帮助开发人员在面对复杂系统时如何进行合理设计的框架,能够提高代码的质量、可维护性和可扩展性。实际开发中,这些原则常常是相互交织和互补的,因此合理的权衡和结合是非常重要的。
依赖倒置原则
依赖倒置原则(Dependency Inversion Principle,DIP) 是面向对象设计中的五大设计原则之一,强调高层模块和低层模块都应该依赖于抽象,而不是依赖于具体实现。这个原则的核心思想是“高层模块不应依赖于低层模块,二者都应依赖于抽象”,从而减少了代码之间的耦合,使得系统更加灵活、可扩展和可维护。
1. 依赖倒置原则的定义
- 高层模块(通常是系统的业务逻辑层)不应该依赖于低层模块(通常是数据库操作、网络通信等底层实现),而应该依赖于抽象。
- 抽象(如接口或抽象类)不应依赖于细节,而细节应依赖于抽象。
2. DIP的主要目的
- 减少模块间的耦合度:通过依赖抽象类或接口,避免了高层模块和低层模块直接相互依赖,使得修改一个模块时不需要修改其他模块。
- 提高可扩展性和可维护性:当需要增加或修改某个低层模块的实现时,不需要修改高层模块的代码,只需要确保新实现遵循相同的接口或抽象类。
- 增强模块间的可替换性:高层模块可以灵活地使用不同的低层实现,只要它们符合相同的接口或抽象类。
3. DIP的两条规则
- 高层模块不应依赖于低层模块,两者都应依赖于抽象。
- 抽象不应依赖于细节,细节应依赖于抽象。
4. DIP的实现方法
- 使用接口或抽象类来定义功能的契约。
- 通过依赖注入(Dependency Injection,DI)或工厂模式等方法将具体实现注入到高层模块。
5. DIP的示例
假设我们有一个业务逻辑模块 OrderService,它需要通过某种方式将订单保存到数据库。传统设计可能是直接在 OrderService 类中创建一个具体的 MySQLDatabase 对象。
不符合DIP的设计:
class MySQLDatabase {
public void save(String data) {
// 代码:将数据保存到MySQL数据库
}
}
class OrderService {
private MySQLDatabase database;
public OrderService() {
database = new MySQLDatabase(); // 直接创建MySQLDatabase对象
}
public void placeOrder(String orderData) {
database.save(orderData); // 使用具体的MySQL数据库类
}
}
在这个设计中,OrderService 直接依赖于 MySQLDatabase,这违反了依赖倒置原则。OrderService 是高层模块,MySQLDatabase 是低层模块。若想将 MySQLDatabase 替换成 PostgreSQLDatabase,我们需要修改 OrderService 类,这会导致高耦合。
符合DIP的设计:
interface Database {
void save(String data);
}
class MySQLDatabase implements Database {
public void save(String data) {
// 代码:将数据保存到MySQL数据库
}
}
class PostgreSQLDatabase implements Database {
public void save(String data) {
// 代码:将数据保存到PostgreSQL数据库
}
}
class OrderService {
private Database database;
// 通过构造函数注入依赖
public OrderService(Database database) {
this.database = database;
}
public void placeOrder(String orderData) {
database.save(orderData); // 使用抽象接口进行交互
}
}
在这个设计中,OrderService 不再直接依赖于具体的数据库实现,而是依赖于 Database 接口(抽象)。这样,无论是 MySQLDatabase 还是 PostgreSQLDatabase,OrderService 都能够使用它们,只要它们实现了 Database 接口。
依赖注入
依赖注入是实现依赖倒置原则的一种常见方法。在上面的例子中,我们通过构造函数注入(Constructor Injection)将数据库依赖传递给 OrderService,也可以通过 setter 方法或其他方式来注入依赖。
// 使用依赖注入来创建 OrderService 对象
Database database = new MySQLDatabase();
OrderService orderService = new OrderService(database);
orderService.placeOrder("order data");
6. DIP的好处
- 降低耦合度:模块之间的依赖关系通过抽象层来隔离,使得高层模块和低层模块可以独立变化。
- 增强灵活性和可扩展性:可以轻松地替换低层模块(如数据库实现)而不影响高层模块的功能。
- 提高代码的可维护性:修改具体实现时,不需要修改高层业务逻辑模块,减少了潜在的错误和回归测试的范围。
7. DIP的挑战
- 增加系统复杂度:为了遵循依赖倒置原则,系统的设计可能变得更加抽象和复杂。需要合理设计接口和抽象类,避免设计过度。
- 学习成本:理解并实施依赖倒置原则可能需要一定的学习曲线,尤其是在大型项目中,如何设计合理的接口和抽象类是一个挑战。
8. 总结
依赖倒置原则(DIP)是减少高层和低层模块耦合度的重要设计原则。通过引入抽象类或接口,高层模块和低层模块都依赖于抽象,而不是直接依赖于具体实现。这样做可以使得代码更加灵活、可扩展和可维护,同时增强了模块的可替换性和可测试性。
在实际项目中,遵循DIP原则能帮助开发者构建松耦合、易于扩展和维护的系统。
明白了,就是高层类用底层类的时候,用接口,而不是用具体类,这样接口实现修改了,高层类也不会修改,
所谓的依赖注入,就是说 A 类 有一个 字段是 B类, 所以 A 类 对 B类 是有依赖的,这个时候我们通过 构造函数 传参的方式,把 这个 B类依赖 注入进去,所以叫做依赖注入。与依赖注入区别的就是 依赖自己创建,这样一来就增加了 耦合度, 比如 这个B类依赖需要修改的时候, 就需要去修改 A 类, 但是如果是注入的话, 修改的地方就少了。
命名学真的,
大学学spring的时候,先学的jdbc ,然后换 spring , 对比讲过 spring 的ioc ,di/dc 依赖注入,控制反转,
在学习Spring框架时,很多同学都会经历从JDBC到Spring的过渡。你提到的IOC(控制反转)和DI/DC(依赖注入)是Spring框架的核心概念之一,它们的作用可以有效简化代码的复杂度,提高代码的可维护性和灵活性。下面我们可以对比一下JDBC和Spring在这些方面的差异。
1. JDBC与Spring的差异
JDBC
在JDBC编程中,数据库连接、SQL执行和结果处理都是由程序员手动管理的。你需要自己创建连接对象、编写SQL查询、处理结果集、管理事务等。这种方式非常繁琐,且容易出错。
例如,典型的JDBC代码大致如下:
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
String username = rs.getString("username");
// 处理结果
}
rs.close();
stmt.close();
conn.close();
上面的代码中,你需要关注数据库连接的管理、SQL执行和结果集的处理,这些代码的耦合性很高,且容易出错,尤其是在连接池管理和事务控制方面。
Spring
Spring框架通过IOC和DI简化了这些操作。你不需要手动创建数据库连接,而是交给Spring容器管理。Spring会自动将所需的依赖(如数据库连接池、DataSource、事务管理器等)注入到你的类中,从而让你专注于业务逻辑。
Spring的方式看起来像这样:
@Autowired
private JdbcTemplate jdbcTemplate;
public void getUsers() {
String sql = "SELECT * FROM users";
List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
// 处理查询结果
}
在上面的代码中,Spring负责创建和管理JdbcTemplate实例,并通过依赖注入将它注入到getUsers方法所在的类中。这样,你的代码更加简洁、解耦,并且可以专注于查询业务而不需要担心底层的数据库连接、SQL执行和结果处理。
2. IOC与DI的区别
Spring框架的核心就是控制反转(IOC)和依赖注入(DI),这两者密切相关,但概念上有所区别:
IOC(控制反转)
IOC指的是对象的控制反转,也就是说,传统的面向对象编程中,类通常会主动创建依赖的对象(比如在JDBC中手动创建连接、Statement等),而在Spring中,控制权被反转给Spring容器,容器负责创建和管理对象的生命周期。
通过IOC,Spring将对象的创建和管理工作交给了Spring容器,程序员无需关心这些对象的创建、销毁及依赖关系的维护。
DI(依赖注入)
DI是IOC的实现方式之一,它通过将依赖对象(如JdbcTemplate)注入到目标类中来解耦。你可以通过不同的方式注入依赖,比如构造函数注入、Setter注入或字段注入。
比如:
@Component
public class UserService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public UserService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<User> getUsers() {
return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class));
}
}
在上面的代码中,JdbcTemplate是通过构造函数注入的,这样UserService不需要自己创建JdbcTemplate实例,而是由Spring自动提供。
3. Spring的优点
通过IOC和DI,Spring提供了以下几个显著的优点:
- 解耦:通过将对象的创建和管理交给Spring容器,降低了类之间的耦合度。
- 便于单元测试:由于对象的依赖可以通过DI注入,单元测试时可以方便地替换依赖项(比如使用Mock对象)。
- 增强的灵活性:通过Spring配置文件或注解,可以非常灵活地配置和调整对象的创建与依赖关系。
- 统一的事务管理:Spring提供了一个统一的事务管理模型,可以非常方便地管理数据库事务,减少了手动事务控制的复杂度。
总结
- JDBC和Spring的主要差异在于,Spring通过IOC和DI将代码中的依赖管理、事务控制、数据库连接等交给容器处理,而JDBC则需要手动管理这些操作。
- IOC是Spring容器管理对象生命周期的方式,DI则是IOC的一种实现,主要通过将对象依赖注入到类中来解耦代码。
掌握了Spring的IOC和DI后,你可以更专注于业务逻辑的编写,而不必关心对象的创建、依赖管理和资源的释放等细节,这大大提高了开发效率和代码质量。
依赖倒置原则(DIP)的核心思想。简单来说,就是高层类通过接口与底层类进行交互,而不是直接依赖具体的实现。
具体来说:
- 高层类(例如,业务逻辑层)只依赖接口或抽象类,这样它就不需要关心底层类的具体实现。
- 底层类(例如,数据访问层、网络层等)实现接口或继承抽象类,从而可以替换成其他不同的实现,而不影响高层类。
举个例子:
假设你有一个 OrderService 类,它依赖于一个数据库类来保存订单信息。按照 DIP 的原则,OrderService 应该依赖于 Database 接口,而不是具体的 MySQLDatabase 类。
// 定义一个抽象接口
interface Database {
void save(String data);
}
// 高层类依赖于接口
class OrderService {
private Database database;
public OrderService(Database database) {
this.database = database; // 依赖注入
}
public void placeOrder(String orderData) {
database.save(orderData); // 通过接口调用底层功能
}
}
// 低层类实现接口
class MySQLDatabase implements Database {
public void save(String data) {
// 保存到 MySQL 数据库
}
}
class PostgreSQLDatabase implements Database {
public void save(String data) {
// 保存到 PostgreSQL 数据库
}
}
这样,OrderService 类就不依赖具体的 MySQLDatabase 或 PostgreSQLDatabase,它只依赖于 Database 接口。当你需要更换底层数据库实现时,OrderService 类不需要修改,只需要传入一个新的数据库实现类即可。
你总结得很到位:
- 接口的实现可以随时修改,不会影响到高层类。
- 高层类只要依赖接口,底层实现的变化不会对它产生任何影响。
这样就达到了降低耦合、增强灵活性的目的。
依赖注入,控制反转, 很简单的东西,就是命名玄乎,
依赖注入,简单来说,就是 用 接口注入的方式,把依赖的对象传进去,解决 依赖由高层类自己创建 带来的 耦合问题。
控制反转,简单来说,就是 对象的创建,交给 spring框架,简化开发,不用自己去管理对象的创建和销毁。
你对依赖注入(DI)和控制反转(IOC)的理解已经很到位了!你把它们的概念用简单的语言阐述得非常清晰。确实,这两个概念常常被过度“包装”,但它们的核心思想其实相当直白。
为了进一步澄清,让我们再简单总结一下:
1. 依赖注入(DI)
依赖注入就是把一个对象所依赖的其他对象传递给它,而不是让对象自己去创建这些依赖。这样做的好处是:
- 降低耦合度:高层类不再自己创建低层类的实例,而是通过注入的方式由外部提供这些依赖。
- 增强灵活性和可测试性:你可以在不同的环境下(如测试环境)轻松替换依赖,甚至使用Mock对象。
简单的例子:
public class UserService {
private UserRepository userRepository;
// 通过构造函数注入依赖
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(User user) {
userRepository.save(user);
}
}
这里的UserService依赖UserRepository,但是它并没有去创建UserRepository,而是通过构造函数将其注入。这样UserService就不再与具体的UserRepository实现耦合,可以更灵活地替换它(比如使用一个Mock实现)。
2. 控制反转(IOC)
控制反转指的是,程序中的对象的创建、管理和生命周期由容器控制,而不是由应用程序自己控制。在传统的编程中,开发者需要手动创建对象并管理它们的生命周期,而在Spring中,容器负责这些工作。
简单来说,Spring会反转控制,将对象的创建、配置、依赖关系的注入等操作交给Spring框架来做。开发者不再需要显式地去管理对象的创建,而是通过配置(XML、注解等)告诉Spring如何创建对象,Spring会负责所有的依赖注入和对象管理。
比如,在Spring中,你只需要定义一个类,并使用注解或XML配置声明它的依赖关系,Spring容器就会自动创建和管理这些对象:
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUser(User user) {
userRepository.save(user);
}
}
Spring会负责:
- 自动实例化
UserService。 - 自动为
UserService注入UserRepository(通过@Autowired)。
总结
- 依赖注入(DI):将对象所依赖的其他对象传入,而不是让对象自己去创建它们。这避免了高层类和低层类之间的耦合。
- 控制反转(IOC):框架控制对象的创建、生命周期等,而不是由应用程序显式控制。Spring容器负责管理应用程序中对象的实例化、配置、依赖关系注入等。
就像你说的,这些概念本质上并不复杂,关键在于让程序的结构变得更清晰、灵活和易于管理,而不需要开发者去手动管理对象的创建和销毁。
视频很垃圾,跳着看,弹幕一直在吐槽,垃圾视频,
确实例子很垃圾,还没gpt 讲的好,
直接用 gpt 例子就行了,故意讲的迷糊,自己也不懂,纯念文字,
设计模式我会很快搞定,
这个东西本来就内容不多不难
很快过,
然后就是 jvm , spring , springmvc ,
mongodb, postgresql , kafka , es , k8s , jenkins ,
学无可学,面试无敌,
接口隔离原则
接口隔离原则强调,应该将大的、复杂的接口拆分成多个小的、专门化的接口。每个接口都只提供与客户端相关的功能,避免一个客户端不需要使用到某些功能而强行去依赖这些功能。
举个例子
假设你有一个 Printer 接口,定义了多种打印相关的功能:
public interface Printer {
void printDocument();
void scanDocument();
void faxDocument();
}
如果某些客户端只需要打印文档,并不需要扫描或传真文档,那么按照接口隔离原则,它们不应该被强制依赖于 scanDocument() 或 faxDocument() 这些方法。理想的做法是,将这个接口拆分成多个专门化的接口:
public interface Printer {
void printDocument();
}
public interface Scanner {
void scanDocument();
}
public interface Fax {
void faxDocument();
}
这样,客户端只需要依赖它需要的接口。例如,某个客户端只需要打印功能,可以依赖 Printer 接口;而只需要扫描功能的客户端,可以依赖 Scanner 接口。
为什么要遵循接口隔离原则?
- 减少依赖:让客户端只依赖它需要的接口,避免不必要的依赖关系,使得系统的耦合度降低,增加了代码的灵活性。
- 提高可维护性:由于接口变得更小且专注,维护起来会更加简洁,不容易出现修改一个功能导致其他不相关功能出问题的情况。
- 提高可扩展性:如果需求变更,只需要修改相关接口,不必去考虑不相关的接口。
- 增加系统的可理解性:每个接口职责单一,功能清晰,其他开发人员在阅读代码时容易理解。
总结
接口隔离原则的目的是优化设计,避免接口臃肿、功能混杂,进而提高系统的可维护性和扩展性。通过拆分接口,能够让客户端只依赖于其实际需要的部分,从而提升系统的灵活性和可扩展性。
就是接口不要写很多抽象方法,这样实现类就要实现很多不需要用到的抽象方法, 比如 之前学习 juc 的时候,AQS 就没有 抽象方法,因为很多类都会实现这接口,这样就不需要去实现所有的抽象方法。
迪米特法则
迪米特法则,就是 类和类之间, 要有边界感,这样也是避免了耦合,比如 我人 对象, 开 汽车 对象, 我调用 汽车对象 提供的 驾驶方法, 我不需要去了解 内部细节,只调用 汽车给我提供的外部接口
























这几集完全没用,全是废话













tmd 今天早上来的很早













































被折叠的 条评论
为什么被折叠?



