基本概念
- 封装:将对象的状态和行为封装成一个类,对外提供公共的访问方式,隐藏内部的实现细节。
- 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。
- 继承:继承是从已有类得到继承信息创建新类的过程,提供继承信息的类被称为父类(超类、基类),得到继承信息的类被称为子类(派生类、衍生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
- 多态:多态是指允许不同子类型的对象对同一消息做出不同的响应。多态可以提高代码的可扩展性和复用性。
设计原则
- 单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
- 接口隔离原则:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
- 依赖倒置原则:抽象不应该依赖于细节,细节应该依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
- 里氏替换原则:子类型必须能够替换掉它们的父类型。
- 开闭原则:软件实体应该对扩展开放,对修改关闭。
- 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。
- 合成复用原则:尽量使用对象组合,而不是继承来达到复用的目的。
工厂模式
- 简单工厂模式:由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式实际上不是一种设计模式,反而比较像是一种编程习惯。
- 工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
public abstract class Video{
public abstract void produce();
}
public class JavaVideo extends Video{
@Override
public void produce() {
System.out.println("录制Java视频");
}
}
public class PythonVideo extends Video{
@Override
public void produce() {
System.out.println("录制Python视频");
}
}
public abstract class VideoFactory{
public abstract Video getVideo();
}
public class JavaVideoFactory extends VideoFactory{
@Override
public Video getVideo() {
return new JavaVideo();
}
}
public class PythonVideoFactory extends VideoFactory{
@Override
public Video getVideo() {
return new PythonVideo();
}
}
public class Test {
public static void main(String[] args) {
VideoFactory videoFactory = new JavaVideoFactory();
Video video = videoFactory.getVideo();
video.produce();
System.out.println(video);
}
}
抽象工厂模式
- 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
public abstract class CourseFactory {
public void init(){
System.out.println("初始化基础数据");
}
public abstract Video getVideo();
public abstract Article getArticle();
}
public class JavaCourseFactory extends CourseFactory{
@Override
public Video getVideo() {
return new JavaVideo();
}
@Override
public Article getArticle() {
return new JavaArticle();
}
}
public class PythonCourseFactory extends CourseFactory{
@Override
public Video getVideo() {
return new PythonVideo();
}
@Override
public Article getArticle() {
return new PythonArticle();
}
}
public class Test {
public static void main(String[] args) {
CourseFactory courseFactory = new JavaCourseFactory();
Video video = courseFactory.getVideo();
Article article = courseFactory.getArticle();
video.produce();
article.produce();
}
}
建造者模式
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
public class student{
private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) {
...
}
public static StudentBuilder builder(){
return new StudentBuilder();
}
public static class StudentBuilder{
int id;
int age;
int grade;
String name;
String college;
String profession;
List<String> awards;
public StudentBuilder id(int id){
this.id = id;
return this;
}
public StudentBuilder age(int age){
this.age = age;
return this;
}
public StudentBuilder grade(int grade){
this.grade = grade;
return this;
}
...
public Student build(){
return new Student(id, age, grade, name, college, profession, awards);
}
}
}
public class Test {
public static void main(String[] args) {
Student student = Student.builder().id(1).age(18).grade(3).name("张三").college("计算机学院").profession("计算机科学与技术").awards(Arrays.asList("国家奖学金","国家励志奖学金")).build();
System.out.println(student);
}
}
桥接模式
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。然后之间通过组合的方式来组合起来。
业务场景:一个抽象类有多个子类,每个子类有多个实现类,抽象类和实现类之间通过组合的方式来组合起来。
public interface Account {
// 打开账户
Account openAccount();
// 查看账户类型
void showAccountType();
}
// 定期账户
public class DepositAccount implements Account{
@Override
public Account openAccount() {
System.out.println("打开定期账户");
return new DepositAccount();
}
@Override
public void showAccountType() {
System.out.println("这是一个定期账户");
}
}
// 活期账户
public class SavingAccount implements Account{
@Override
public Account openAccount() {
System.out.println("打开活期账户");
return new SavingAccount();
}
@Override
public void showAccountType() {
System.out.println("这是一个活期账户");
}
}
public abstract class Bank {
// 组合一个Account,这样Bank就聚合了Account
protected Account account;
// 构造器传入一个Account
public Bank(Account account) {
this.account = account;
}
// 委托给Account
abstract Account openAccount();
}
public class ABCBank extends Bank{
// 构造器传入一个Account
public ABCBank(Account account) {
super(account);
}
@Override
Account openAccount() {
System.out.println("打开中国农业银行账号");
// 委托给Account
account.openAccount();
return account;
}
}
public class ICBCBank extends Bank{
public ICBCBank(Account account){
super(account);
}
@Override
Account openAccount() {
System.out.println("打开中国工商银行账号");
account.openAccount();
return account;
}
}
public class Test{
public static void main(String[] args) {
Bank icbcBank = new ICBCBank(new DepositAccount());
Account icbcAccount = icbcBank.openAccount();
icbcAccount.showAccountType();
Bank abcBank = new ABCBank(new SavingAccount());
Account abcAccount = abcBank.openAccount();
abcAccount.showAccountType();
}
}
单例模式
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
// 懒汉式
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
/*
饿汉模式缺点:不能实现懒加载,如果该类没有被使用过,会造成内存浪费。
*/
// 饿汉式
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
/*
饿汉模式:实现了懒加载,节约了内存空间,但是在不加锁的情况下,是线程不安全的。可能出现多份实例,如果加锁,会使得程序串行执行,严重影响性能。
*/
原型模式
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
浅拷贝:对于基本数据类型的成员变量,会直接进行值传递,对于引用类型的成员变量,会进行引用传递,也就是说新对象和原型对象的成员变量指向的是同一个对象。
深拷贝:对于基本数据类型的成员变量,会直接进行值传递,对于引用类型的成员变量,会进行引用传递,同时对引用的对象进行拷贝,也就是说新对象和原型对象的成员变量指向的是不同的对象。
public class Student implements Cloneable{ //注意需要实现Cloneable接口
@Override
public Object clone() throws CloneNotSupportedException { //提升clone方法的访问权限
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Student student0 = new Student();
Student student1 = (Student) student0.clone();
System.out.println(student0);
System.out.println(student1);
}
适配器模式
适配器模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
public interface DC5 {
int outputDC5V();
}
public class AC220 {
public int outputAC220V(){
int output = 220;
System.out.println("输出交流电" + output + "V");
return output;
}
}
public class PowerAdapter implements DC5{
private AC220 ac220 = new AC220();
@Override
public int outputDC5V() {
int adapterInput = ac220.outputAC220V();
// 变压器...
int adapterOutput = adapterInput / 44;
System.out.println("使用PowerAdapter输入AC:" + adapterInput + "V" + "输出DC:" + adapterOutput + "V");
return adapterOutput;
}
}
public class Test {
public static void main(String[] args) {
DC5 dc5 = new PowerAdapter();
dc5.outputDC5V();
}
}
应用场景:SpringMVC中的HandlerAdapter,它就是一个适配器,它的作用是将我们的Handler适配成一个可以执行的Handler。
封装有缺陷的接口设计:接口的设计不可谓不尽善尽美,随着业务的发展,接口的不合理之处会逐渐暴露出来,这个时候我们就需要对接口进行重构,但是重构接口是一件非常费时费力的事情,我们可以通过增加一个适配器来解决这个问题,使用适配器模式可以在不修改原有代码的基础上来复用接口。
统一多个类的接口设计:在开发中,我们可能会使用到很多第三方的SDK,这些SDK中的接口设计可能不太统一,但是我们的系统必须统一对外,这个时候我们就可以使用适配器模式来对这些SDK中的接口进行适配,从而统一对外。
代理模式
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
// 静态代理
public interface IOrderService {
int createOrder(Order order);
}
public class OrderServiceImpl implements IOrderService {
private OrderDao orderDao;
public OrderServiceImpl(){
// 如果使用Spring应该是自动注入的
// 我们为了使用方便,在构造方法中将orderDao直接初始化了
orderDao = new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("OrderService调用orderDao创建订单");
return orderDao.insert(order);
}
}
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao创建Order成功!");
return 1;
}
}
public class OrderServiceStaticProxy {
private IOrderService iOrderService;
public int createOrder(Order order){
before();
iOrderService = new OrderServiceImpl();
int result = iOrderService.createOrder(order);
after();
return result;
}
private void before(){
System.out.println("静态代理 before code");
}
private void after(){
System.out.println("静态代理 after code");
}
}
public class Test {
public static void main(String[] args) {
Order order = new Order();
order.setUserId(2);
OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
orderServiceStaticProxy.createOrder(order);
}
}
应用场景:Spring AOP、事务控制、权限控制、日志记录、性能监控、缓存等。
缺点:静态代理只能为一个接口服务,一个代理类只能实现一个接口,如果需要代理多个接口,就需要建立很多个代理类。
动态代理:动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
public interface IOrderService {
int createOrder(Order order);
}
public class OrderServiceImpl implements IOrderService {
private OrderDao orderDao;
public OrderServiceImpl(){
// 如果使用Spring应该是自动注入的
// 我们为了使用方便,在构造方法中将orderDao直接初始化了
orderDao = new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("OrderService调用orderDao创建订单");
return orderDao.insert(order);
}
}
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao创建Order成功!");
return 1;
}
}
public class OrderServiceDynamicProxy implements InvocationHandler {
private Object target;
public OrderServiceDynamicProxy(Object target){
this.target = target;
}
public Object bind(){
Class cls = target.getClass();
return Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object argObject = args[0];
beforeMethod(argObject);
Object object = method.invoke(target,args);
afterMethod();
return object;
}
private void beforeMethod(Object obj){
int userId = 0;
System.out.println("动态代理 before code");
if(obj instanceof Order){
Order order = (Order)obj;
userId = order.getUserId();
}
// 根据userId进行数据源的切换
int dbRouter = userId % 2;
System.out.println("动态代理分配到【db" + dbRouter + "】处理数据");
// todo 设置dataSource;
DataSourceContextHolder.setDBType("db" + String.valueOf(dbRouter));
}
private void afterMethod(){
System.out.println("动态代理 after code");
}
}
public class Test {
public static void main(String[] args) {
Order order = new Order();
order.setUserId(2);
IOrderService orderService = (IOrderService)new OrderServiceDynamicProxy(new OrderServiceImpl()).bind();
orderService.createOrder(order);
}
}
JDK动态代理的五个步骤:
- 通过实现InvocationHandler接口创建自己的调用处理器;
- 通过Proxy.getProxyClass获得动态代理类;
- 通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class);
- 通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入;
- 通过代理对象调用目标方法。
CGlib动态代理的三个步骤:
- 通过Enhancer类创建代理类;
- 实现MethodInterceptor接口并重写intercept方法;
- 使用的时候通过代理类的create方法创建代理类对象。
组合模式
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
/**
* 首先创建一个组件抽象,组件可以包含组件,组件有自己的业务方法
*/
public abstract class Component {
public abstract void addComponent(Component component); //添加子组件
public abstract void removeComponent(Component component); //删除子组件
public abstract Component getChild(int index); //获取子组件
public abstract void test(); //执行对应的业务方法,比如修改文件名称
}
/**
* 创建一个叶子组件,也就是不包含其他组件的组件,这个组件里面有自己的业务方法
*/
public class Dirrectory extends Component{
List<Component> list = new ArrayList<>();
@Override
public void addComponent(Component component) {
list.add(component);
}
@Override
public void removeComponent(Component component) {
list.remove(component);
}
@Override
public Component getChild(int index) {
return list.get(index);
}
@Override
public void test() {
System.out.println("执行文件夹的业务方法");
}
}
public class File extends Component{
@Override
public void addComponent(Component component) {
System.out.println("文件不支持添加组件");
}
@Override
public void removeComponent(Component component) {
System.out.println("文件不支持删除组件");
}
@Override
public Component getChild(int index) {
System.out.println("文件不支持获取子组件");
return null;
}
@Override
public void test() {
System.out.println("执行文件的业务方法");
}
}
public class Test {
public static void main(String[] args) {
Directory outer = new Directory(); //新建一个外层目录
Directory inner = new Directory(); //新建一个内层目录
outer.addComponent(inner);
outer.addComponent(new File()); //在内层目录和外层目录都添加点文件,注意别导错包了
inner.addComponent(new File());
inner.addComponent(new File());
outer.test(); //开始执行文件名称修改操作
}
}
解释器模式
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
模板方法模式
模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
/**
* 抽象诊断方法,因为现在只知道挂号和看医生是固定模式,剩下的开处方和拿药都是不确定的
*/
public abstract class AbstractDiagnosis {
public void test(){
System.out.println("今天头好晕,不想起床,开摆,先跟公司请个假");
System.out.println("去医院看病了~");
System.out.println("1 >> 先挂号");
System.out.println("2 >> 等待叫号");
//由于现在不知道该开什么处方,所以只能先定义一下行为,然后具体由子类实现
//大致的流程先定义好就行
this.prescribe();
this.medicine(); //开药同理
}
public abstract void prescribe(); //开处方操作根据具体病症决定了
public abstract void medicine(); //拿药也是根据具体的处方去拿
}
/**
* 感冒相关的具体实现子类
*/
public class ColdDiagnosis extends AbstractDiagnosis{
@Override
public void prescribe() {
System.out.println("3 >> 一眼丁真,鉴定为假,你这不是感冒,纯粹是想摆烂");
}
@Override
public void medicine() {
System.out.println("4 >> 开点头孢回去吃吧");
}
}
public static void main(String[] args) {
AbstractDiagnosis diagnosis = new ColdDiagnosis();
diagnosis.test();
}
责任链模式
责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。比如Filter过滤器。
public abstract class Handler{
protected Handler successor;
public Handler connect(Handler successor){
this.successor = successor;
return successor;
}
public void handle(){
this.doHandle();
Optional.ofNullable(successor).ifPresent(Handler::handle);
}
public abstract void doHandle();
}
public class FirstHandler extends Handler{ //用于一面的处理器
@Override
public void doHandle() {
System.out.println("============= 白马程序员一面 ==========");
System.out.println("1. 谈谈你对static关键字的理解?");
System.out.println("2. 内部类可以调用外部的数据吗?如果是静态的呢?");
System.out.println("3. hashCode()方法是所有的类都有吗?默认返回的是什么呢?");
System.out.println("以上问题会的,可以依次打在评论区");
}
}
public class ThirdHandler extends Handler{
@Override
public void doHandle() {
System.out.println("============= 白马程序员三面 ==========");
System.out.println("1. synchronized关键字了解吗?如何使用?底层是如何实现的?");
System.out.println("2. IO和NIO的区别在哪里?NIO三大核心组件?");
System.out.println("3. TCP握手和挥手流程?少一次握手可以吗?为什么?");
System.out.println("4. 操作系统中PCB是做什么的?运行机制是什么?");
System.out.println("以上问题会的,可以依次打在评论区");
}
}
public class ThirdHandler extends Handler{
@Override
public void doHandle() {
System.out.println("============= 白马程序员三面 ==========");
System.out.println("1. synchronized关键字了解吗?如何使用?底层是如何实现的?");
System.out.println("2. IO和NIO的区别在哪里?NIO三大核心组件?");
System.out.println("3. TCP握手和挥手流程?少一次握手可以吗?为什么?");
System.out.println("4. 操作系统中PCB是做什么的?运行机制是什么?");
System.out.println("以上问题会的,可以依次打在评论区");
}
}
public static void main(){
Handler handler = new FirstHandler();
handler.connect(new SecondHandler()).connect(new ThirdHandler());
}
命令模式
命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
public interface Receiver {
void action(); //具体行为,这里就写一个算了
}
public abstract class Command { //指令抽象,不同的电器有指令
private final Receiver receiver;
protected Command(Receiver receiver){ //指定此命令对应的电器(接受者)
this.receiver = receiver;
}
public void execute() {
receiver.action(); //执行命令,实际上就是让接收者开始干活
}
}
public class Controller { //遥控器只需要把我们的指令发出去就行了
public static void call(Command command){
command.execute();
}
}
public class AirConditioner implements Receiver{
@Override
public void action() {
System.out.println("空调已开启,呼呼呼");
}
}
public class OpenCommand extends Command {
public OpenCommand(AirConditioner airConditioner) {
super(airConditioner);
}
}
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner(); //先创建一个空调
Controller.call(new OpenCommand(airConditioner)); //直接通过遥控器来发送空调开启命令
}
迭代器模式
迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
中介者模式
中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
// 房产中介
public class Mediator {
private final Map<String, User> userMap = new HashMap<>(); //在出售的房子需要存储一下
public void register(String address, User user){ //出售房屋的人,需要告诉中介他的房屋在哪里
userMap.put(address, user);
}
public User find(String address){ //通过此方法来看看有没有对应的房源
return userMap.get(address);
}
}
// 用户
public class User { //用户可以是出售房屋的一方,也可以是寻找房屋的一方
String name;
String tel;
public User(String name, String tel) {
this.name = name;
this.tel = tel;
}
public User find(String address, Mediator mediator){ //找房子的话,需要一个中介和你具体想找的地方
return mediator.find(address);
}
@Override
public String toString() {
return name+" (电话:"+tel+")";
}
}
public static void main(String[] args) {
User user0 = new User("刘女士", "10086"); //出租人
User user1 = new User("李先生", "10010"); //找房人
Mediator mediator = new Mediator(); //我是黑心中介
mediator.register("成都市武侯区天府五街白马程序员", user0); //先把房子给中介挂上去
User user = user1.find("成都市武侯区天府五街下硅谷", mediator); //开始找房子
if(user == null) System.out.println("没有找到对应的房源");
user = user1.find("成都市武侯区天府五街白马程序员", mediator); //开始找房子
System.out.println(user); //成功找到对应房源
}
备忘录模式
备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
public class Student {
private String currentWork; //当前正在做的事情
private int percentage; //当前的工作完成百分比
public void work(String currentWork) {
this.currentWork = currentWork;
this.percentage = new Random().nextInt(100);
}
@Override
public String toString() {
return "我现在正在做:"+currentWork+" (进度:"+percentage+"%)";
}
}
public class State {
final String currentWork;
final int percentage;
State(String currentWork, int percentage) { //仅开放给同一个包下的Student类使用
this.currentWork = currentWork;
this.percentage = percentage;
}
}
public class Student {
...
public State save(){
return new State(currentWork, percentage);
}
public void restore(State state){
this.currentWork = state.currentWork;
this.percentage = state.percentage;
}
...
}
public static void main(String[] args) {
Student student = new Student();
student.work("学Java"); //开始学Java
System.out.println(student);
State savedState = student.save(); //保存一下当前的状态
student.work("打电动"); //刚打开B站播放视频,学一半开始摆烂了
System.out.println(student);
student.restore(savedState); //两级反转!回到上一个保存的状态
System.out.println(student); //回到学Java的状态
}
观察者模式
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
public interface Observer {
void update(String message);
}
public class Subject {
private final Set<Observer> observerSet = new HashSet<>();
public void observe(Observer observer) { //添加观察者
observerSet.add(observer);
}
public void modify() { //模拟对象进行修改
observerSet.forEach(Observer::update); //当对象发生修改时,会通知所有的观察者,并进行方法回调
}
}
public static void main(String[] args) {
Subject subject = new Subject();
subject.observe(() -> System.out.println("我是一号观察者!"));
subject.observe(() -> System.out.println("我是二号观察者!"));
subject.modify();
}
状态模式
状态模式:当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。
策略模式
策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。例如线程池的拒绝策略。
访问者模式
访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。