重构·改善既有代码的设计.04之重构手法(下)完结

1. 前言

本文是代码重构系列的最后一篇啦。前面三篇《重构·改善既有代码的设计.01之入门基础》、《重构·改善既有代码的设计.02之代码的“坏味道”》、《重构·改善既有代码的设计.03之重构手法(上)》介绍了基础入门,代码异味,还有部分重构手法。今天继续总结重构手法下篇,从条件表达式、函数调用、以及类继承关系上阐述了各种重构手法,希望对项目能有所帮助。另外本文更新后该系列就完结了,感谢各位看官的指点。
在这里插入图片描述

2. 简化条件表达式

“分支逻辑”和“操作细节”分离。

1、Decompose Conditional 分解条件表达式。

复杂的条件语句(if-then-else)。
改造前:

if(date.before(SUMMER_START) || date.after(SUMMER_END)) {
    charge = quantity * _winterRate * _winterServiceCharge;
} else {
    charge = quantity * _summerRate;
}

改造后:

if(notSummer(date)) {
    charge = winterCharge(quantity);
} else {
    charge = summerCharge(quantity);
}

private boolean notSummer(Date date) {
    return date.before(SUMMER_START) || date.after(SUMMER_END);
}

private double summerCharge(int quantity) {
    return quantity * _summerRate;
}

private double winterCharge(int quantity) {
    return quantity * _winterRate * _winterServiceCharge;
}

2、Consolidate Conditional Expression 合并条件表达式。

有一系列条件测试,都得到相同结果。将这些测试合并为一个条件表达式,并将这个条件表达式提炼成一个独立函数。
改造前:

double disabilityAmount(){
    if(_seniority < 2) {
        return 0;    
    }
    if(_monthDisabled > 12) {
        return 0;    
    }
    if(_isPartTime) {
        return 0;    
    }
    // todo...
}

改造后:

double disabilityAmount(){
    if((_seniority < 2) || (_monthDisabled > 12) || _isPartTime) {
        return 0;    
    }
    // todo...
}

或:
double disabilityAmount(){
    if(isDisability()) {
        return 0;    
    }
    // todo...
}

boolean isDisability() {
    return (_seniority < 2) || (_monthDisabled > 12) || _isPartTime;
}

3、Consolidate Duplicate Conditional Fragments 合并重复的条件片段。

条件表达式的每个分支上有着相同的一段代码。
改造前:

if(isSpecialDeal()) {
    total = price * 0.95;
    send();
} else {
    total = price * 0.98;
    send();
}

改造后:

if(isSpecialDeal()) {
    total = price * 0.95;
} else {
    total = price * 0.98;
}

send();

4、Remove Control Flag 移除控制标记。

循环体中,通常需要判断何时停止条件检查。有时会引入某个控制变量来起到循环判断的作用。建议以break或continue或return 语句取代控制标记。
改造前:

void check(String[] person) {
    boolean found = false;   // 控制标记
    for(int i = 0; i < person.length; i++) {
        if(!found) {
            if(person[i] == "tom") {
                found = true;
                sendAlert();                
            }
            if(person[i] == "jose") {
                found = true;
                sendAlert();                
            }          
        }    
    }
    
}

改造后:

void check(String[] person) {
    boolean found = false;   // 控制标记
    for(int i = 0; i < person.length; i++) {
        if(!found) {
            if(person[i] == "tom") {
                sendAlert();
                break;                
            }
            if(person[i] == "jose") {
                sendAlert();    
                break;            
            }          
        }    
    }
    
}

5、Replace Nested Conditional with Guard Clauses 以卫语句取代嵌套条件表达式。

条件表达式中,如果两条分支都是正常行为,使用形如if…else…的条件表达式;如果某个条件极为罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回(如参数校验判断)。这样的单检查称为“卫语句”。
卫语句要么就从函数中返回,要么就抛出一个异常。
改造前:

double getPayment(){
    double result;
    if(_isDead) {
        result = deadAmount();    
    } else {
        if(_isSeparated) {
            result = separatedAmount();        
        } else {
            if(_isRetired) {
                result = retiredAmount();            
            } else {
                result = normalAmount();            
            }       
        }    
    }
    
    return result;
}

改造后:

double getPayment(){
    if(_isDead) {
        return deadAmount();    
    }
    if(_isSeparated) {
       return separatedAmount();        
    }
    if(_isRetired) {
       return retiredAmount();    
    }        
        
    return normalAmount(); 
}

6、Replace Conditional with Polymorphism 以多态取代条件表达式。

改造前:

class Employee {
    private EmployeeType _type;
    
    int payment() {
        switch(getType()) {
            case EmployeeType.ENGINEER:
                return _monthlySalary;
            case EmployeeType.SALESMAN:
                return _monthlySalary + _commission;
            case EmployeeType.MANAGER:
                return _monthlySalary + _bonus;
            default:
                throw new RuntimeException("error");                                                    
        }    
    }
    
    int getType() {
        return _type.getTypeCode();    
    }
}

abstract class EmployeeType {
    abstract int getTypeCode();
}

class Engineer extends EmployeeType {
    int getTypeCode(){
        return EmployeeType.ENGINEER;    
    }
}

class Salesman extends EmployeeType {
    int getTypeCode(){
        return EmployeeType.SALESMAN;    
    }
}

class Manager extends EmployeeType {
    int getTypeCode(){
        return EmployeeType.MANAGER;    
    }
}

改造后:

class Employee {
    private EmployeeType _type;
    
    int payment() {
        return _type.payment();  
    }
}

abstract class EmployeeType {
    abstract int payment(Employee emp);
}

class Engineer extends EmployeeType {
    int payment(Employee emp){
        return emp.getMonthlySalary();   
    }
}

class Salesman extends EmployeeType {
    int payment(Employee emp){
        return emp.getMonthlySalary() + emp.getCommission();   
    }
}

class Manager extends EmployeeType {
    int payment(Employee emp){
        return emp.getMonthlySalary() + emp.getBonus();   
    }
}

7、Introduce Null Object 引入Null对象。

将null替换为null对象。
改造前:

class Site {
    private Customer _customer;
    Customer getCustomer() {
        return _customer;    
    }
}

class Customer {
    public Stirng getName() {...}
    public BillingPlan getPlan(){...}
}

// 调用
Customer customer = site.getCustomer();
if(customer == null) {
    plan = BillingPlan.basic();
} else {
    plan = customer.getPlan();
}
String customerName;
if(customer == null) {
    customerName = "default";
} else {
    customerName = customer.getName();
}

改造后:

class Site {
    private Customer _customer;
    Customer getCustomer() {
        return _customer == ull ? Customer.newNull() : _customer;   
    }
}

class Customer {
    public Stirng getName() {...}
    public BillingPlan getPlan(){...}
    
    public boolean isNull() {
        return false;    
    }
    
    static Customer newNull() {
        return new NullCustomer();    
    }
}

// 定义NullCustomer空对象
class NullCustomer extends Customer{
    public boolean isNull() {
        return true;    
    }
}

// 调用
Customer customer = site.getCustomer();
if(customer.isNull()) {
    plan = BillingPlan.basic();
} else {
    plan = customer.getPlan();
}
String customerName;
if(customer.isNull()) {
    customerName = "default";
} else {
    customerName = customer.getName();
}

8、Introduce Assertion 引入断言。

一段代码需要对程序状态做出某种假设。以断言明确表现这种假设。
改造前:

double getExpenseLimit() {
    return (_expenseLimit != NULL_EXPENSE) ? _expenseLimit : _primaryProject.getMemberExpenseLimit();
}

改造后:

double getExpenseLimit() {
    Assert.isTrue(_expenseLimit != NULL_EXPENSE || _primaryProject != null);
    return (_expenseLimit != NULL_EXPENSE) ? _expenseLimit : _primaryProject.getMemberExpenseLimit();
}

断言,请不要用它来检查“你认为应该为真”的条件,请只使用它来检查“一定必须为真”的条件。请勿滥用。
如果断言所指示的约束条件不能满足,代码是否仍能正常运行? 如果可以,就把断言去掉。

3. 简化函数调用

容易被理解和被使用的接口,是开发良好面向对象软件的关键。

1、Rename Method 函数改名。

将复杂的处理过程分解成小函数。但是如果做的不好,会使你费尽周折却弄不清楚这些小函数各自的用途。要避免这种麻烦,关键在于给函数起一个好名称。
1、尽可能起一个良好名称的函数,顾名思义表达该函数的作用,而不是表达该函数如何做。
2、合理安排函数签名,如果重新安排参数顺序,能够帮助提供代码的清晰度。

2、Add Parameter 添加参数。

为函数添加一个对象参数,让该对象带进函数所需信息。
动机:
你必须修改一个函数,而修改后的函数需要一些过去没有的信息,因此你需要给该函数添加一个参数。
改造前:

double getExpenseLimit(double limit) {
    // todo...
}

// 需要添加一个参数
double getExpenseLimit(double limit, Date date) {
    // todo...
}

改造后:

double getExpenseLimit(ExpenseLimit limitObj) {
    // todo...
    double limit = limitObj.getLimit();
    Date date = limitObj.getDate();
}

class ExpenseLimit {
    double limit;
    Date date;
}

但是需要警惕引用传递。其实我并不推荐整个对象传参。当你传整个参数时,对于这个函数你不能准确的说出这个函数所使用的参数。有可能对象包含了5个参数,而你理论上只需要3个。 这时候宁可将参数依次卸载参数列表中。不过所带来的影响是代码参数过长。如果过长,也是不太友好的。
推荐:适当使用参数列表和对象参数,必要时可以进行函数重载更简洁说明函数意图。比如:

double getExpenseLimit(double limit) {
    this.getExpenseLimit(limit, new Date());
}

double getExpenseLimit(double limit, Date date) {
    ExpenseLimit limitObj = new ExpenseLimit(limit, date);
    this.getExpenseLimit(limitObj);
}

double getExpenseLimit(ExpenseLimit limitObj) {
    // todo...
    double limit = limitObj.getLimit();
    Date date = limitObj.getDate();
}

class ExpenseLimit {
    double limit;
    Date date;
}

3、Remove Parameter 移除参数。

移除函数体无用参数。
改造前:

double getExpenseLimit(double limit, Date date) {
    return limit * 0.98;
}

改造后:

double getExpenseLimit(double limit) {
    return limit * 0.98;
}

4、Separate Query from Modifier 将查询函数和修改函数分离。

某个函数既返回对象状态值,又修改对象状态。

5、Parameterize Method 令函数携带参数。

若干函数做了类似的工作,但在函数本体中却包含了不同的值。
改造前:

class Employee {
    void tenPercentRaise() {
        salary *= 1.1;    
    }
    void fivePercentRaise() {
        salary *= 1.05;    
    }
}

改造后:

class Employee {
    void raise(double factor) {
        salary *= (1 + factor);    
    }
}

6、Replace Parameter with Explicit Methods 以明确函数取代参数。

有一个函数,其中完全取决于参数值而采取不同行动。
改造前:

void setValue(String name, int value) {
    if(name.equals("height")) {
        _height = value;
        return ;    
    }
    if(name.equals("width")) {
        _width = value;
        return ;    
    }
}

改造后:

void setHeight(int value) {
    _height = value;
}
void setWidth(int value) {
    _width = value;
}

7、Preserve Whole Object 保持对象完整。

从某个对象中取出若干值,将它们作为某一个函数调用时的参数。
改造前:

int low = daysTempRange().getLow();
int high = daysTempRange().getHigh();
withinPlan = plan.withinRange(low, high);

改造后:

withinPlan = plan.withinRange(daysTempRange());

注:
该方法总有两面。如果你传的是数值,被调用函数就只依赖于这些数值,而不依赖它们所属的对象。但如果你传递的是整个对象,被调用函数所在的对象就需要依赖参数对象。如果这样,会使你的依赖结构恶化,那么就不该使用该方法。

8、Replace Parameter with Methods 以函数取代参数。

对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。
改造前:

int basePrice = _quantity * _itemPrice;
discountLevel = getDiscountLevel();
double finalPrice = discountedPrice(basePrice, discountLevel);

改造后:

int basePrice = _quantity * _itemPrice;
double finalPrice = discountedPrice(basePrice);

double discountedPrice(int basePrice) {
    discountLevel = getDiscountLevel();
    ......
}

9、Introduce Parameter Object 引入参数对象。

某些参数总是很自然地同时出现。以一个对象取代这些参数。
改造前:

void amountInvoicedIn(Date start, Date end);
void amountReceicedIn(Date start, Date end);
void amountOverdueIn(Date start, Date end);

改造后:

void amountInvoicedIn(DateRange dateRange);
void amountReceicedIn(DateRange dateRange);
void amountOverdueIn(DateRange dateRange);

class DateRange {
    Date start;
    Date end;
    ...
}

10、Remove Setting Method 移除设值函数。

类中的某个字段应该在对象创建时被设值,然后就不再改变。
改造前:

class Acount {
    private String _id;
    
    Acount(String id) {
        setId(id);    
    }
    
    public void setId(String arg) {
            _id = arg;
    }
}

改造后:

class Acount {
    private final String _id;
    
    Acount(String id) {
        _id = id; 
    }
}

一般我们在处理多线程运算的时候,此类方式使用的比较多。

11、Hide Method 隐藏函数。

有一个函数,从来没有被其他任何类使用到。将这个函数修改为private。

12、Replace Constructor with Factory Method 以工厂函数取代构造函数。

你希望在创建对象时,不仅仅是简单的构建动作。
改造前:

Employee (int type) {
    _type = type;
}

改造后:

static Employee create(int type) {
    return new Employee(type);
}

13、Encapsulate Downcast 封装向下转型。

某个函数返回的对象,需要由函数调用者执行向下转型。将向下转型动作移动到函数中。
改造前:

Object lastReading() {
    return readings.lastElement();
}

改造后:

Reading lastReading() {
    return (Reading) readings.lastEmelent();
}

14、Replace Error Code with Exception 以异常取代错误码。

某个函数返回一个特定的代码,用以表示某种错误情况。
改造前:

int withdraw(int amount) {
    if(amount > _balance) {
        return -1;    
    } else {
        _balance -= amount;
        return 0;    
    }
}

改造后:

void withdraw(int amount) throws BalanceException {
    if(amount > _balance) {
        throw new BalanceException("xxx");
    }
    _balance -= amount;
}

15、Replace Exception with Test 以测试取代异常。

面对一个调用者可以预先检查的条件,你抛出了一个异常。
改造前:

double getValueForPeriod(int period) {
    try {
        return _values[period];    
    } catch(ArrayIndexOutofBoundsException e) {
        return 0;    
    }
}

改造后:

double getValueForPeriod(int period) {
    if(period >= _values.length) {
        return 0;    
    }
    return _values[period];  
}

异常只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误行为,而不应该成为条件检查的替代品。如果你可以合理期望调用者在调用函数之前先检查某个条件,那么就应该提供一个测试,而调用者应该使用它。

4. 处理概括关系

专门用来处理类的概括关系(继承关系),其中主要是将函数上下移动于继承体系之中。

1、Pull Up Field 字段上移。

两个子类拥有相同的字段。将该字段移至超类。
改造前:

class Employee{
    ...
}

class Salesman extends Employee {
    String name;
    ...
}

class Manager extends Employee {
    String name;
    ...
}

改造后:

class Employee{
    String name;
}

class Salesman extends Employee {
    ...
}

class Manager extends Employee {
    ...
}

2、Pull Up Method 函数上移。

有些函数,在各个子类中产生完全相同的结果。
改造前:

class Employee{
    ...
}

class Salesman extends Employee {
    String getName(){
        return _name;    
    }
    ...
}

class Manager extends Employee {
    String getName(){
        return _name;    
    }
    ...
}

改造后:

class Employee {
    String getName(){
        return _name;    
    }
}

class Salesman extends Employee {
    ...
}

class Manager extends Employee {
    ...
}

3、Pull Up Constructor Body 构造函数本体上移。

各个子类中拥有一些构造函数,他们的本地几乎完全一致。
改造前:

class Employee {
    String _id;
    String _name;
}

class Manager extends Employee {
    int _grade;
    
    public Manager(String name, String id, int grade) {
        _id = id;
        _name = name;
        _grade = grade;
    }
}

改造后:

class Employee {
    String _id;
    String _name;
    
    Employee(String name, String id) {
        _id = id;
        _name = name;    
    }
}

class Manager extends Employee {
    int _grade;
    
    public Manager(String name, String id, int grade) {
        super(name, id);
        _grade = grade;
    }
}

4、Push Down Method 函数下移。

超类中的某个函数只与部分(而非全部)子类有关。将这个函数移到相关的那些子类去。恰好与函数上移相反。

5、Push DOwn Field 字段下移。

超类中的某个字段只被部分(而非全部)子类用到。恰好与字段上移相反。

6、Extract Subclass 提炼子类。

类中某些特性只被某些(而非全部)实例用到。可以新建一个子类,将上面所说的那一部分特性移到子类中。

7、Extract Superclass 提炼超类。

两个类具有相似特性。为这两个类建立一个超类,将相同特性移至超类。与提炼子类相反。
改造前:

class Cat {
    void eat(){
     ...       
    }
    void miaomiao(){
        ...    
    }
}

class Bird {
    void eat(){
     ...       
    }
    void fly(){
        ...    
    }
}

改造后:

class Animal {
   void eat(){
     ...       
   }
}

class Cat extends Animal {
    void miaomiao(){
        ...    
    }
}

class Bird extends Animal {
    void fly(){
        ...    
    }
}

8、Extract Interface 提炼接口。

改造前:

double charge(Employee emp, int days) {
   int base = emp.getRate() * days;
   if(emp.hasSpecialSkill()) {
       return base * 1.05;            
   } else {
       return base;    
   }
}

改造后:

interface Billable {
    public int getRate();
    public boolean hasSpecialSkill();
}

class Employee implements Billable {
    double charge(Billable billable, int days) {
    int base = billable.getRate() * days;
    if(billable.hasSpecialSkill()) {
        return base * 1.05;            
    } else {
        return base;    
    }
}

当如果有若干个子类都实现了Billable的接口,他就会很有用。

9、Collapse Hierachy 折叠继承体系。

超类和子类之间无太大区别,可以将他们合为一体。

10、Form Template Method 塑造模板函数。

有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。可以将这些操作分别放进独立函数中,并保持他们都有相同的签名,于是源函数也就变得相同了。然后将源函数上移至超类中。

11、Replace Inheritance with Delegation 以委托取代继承。

某个子类只使用了超类接口的一部分,或是根本不需要继承而来的数据。
改造前:

// 这里的MyStack只需要push()、push()、size()、isEmpty()这四个函数。继承了vector只是因为使用了size()和isEmpty()方法
class MyStack extends Vector {
   public void push(Object element) {
       insertElementAt(element, 0);    
   }
   
   public Object pop(){
       Object result = firstElement();
       removeElementAt(0);
       return result;    
   }
}

改造后:

// 这里将继承改为类委托
class MyStack {
    Vector _vector;
    public void push(Object element) {
        _vector.insertElementAt(element, 0);    
    }
    
    public Object pop(){
        Object result = _vector.firstElement();
        _vector.removeElementAt(0);
        return result;    
    }
    
    public int size() {
        return _vector.size();    
    }
    
    public boolean isEmpty() {
        return _vector.isEmpty();    
    }
}

12、Replace Delegation with Inheritance 以继承取代委托。

与Replace Inheritance with Delegation刚好相反。在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。

5. 小结

到此已经汇总了书中全部的重构手法,依旧保持个人观点,部分重构手法是以牺牲一定的代码阅读性为代价。且书中提到的多数重构手法还是要视具体编程场景而定。避免错误引用。
重构手法和设计模式一样,均为编程模式中的最佳实践。是符合大多数场景和行为的思想或方法的总结。记住是大多数。了解最佳实践有助于提高平常的编码习惯以及提升代码的维护性,可修改性。但如果被错误引用,程序将因为过度设计或引用而变得臃肿。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/960.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java】你真的懂封装吗?一文读懂封装-----建议收藏

博主简介&#xff1a;努力学习的预备程序媛一枚~博主主页&#xff1a; 是瑶瑶子啦所属专栏: Java岛冒险记【从小白到大佬之路】 前言 write in the front: 如何理解封装&#xff1f; 试想&#xff1a;我们使用微波炉的时候&#xff0c;只用设置好时间&#xff0c;按下“开始”…

[C++]反向迭代器

目录 前言&#xff1a; 1 对反向迭代器的构造思想 2 实现反向迭代器 3 完整代码 前言&#xff1a; 本篇文章主要介绍了STL容器当中的反向迭代器&#xff0c;可能有朋友会说&#xff1a;“反向迭代器有什么好学的&#xff1f;不一样还是迭代器吗&#xff0c;我正向能写出来&…

【js逆向】hook大全

▒ 目录 ▒&#x1f6eb; 导读需求1️⃣ 普通函数2️⃣ 对象方法&#xff08;Class.prototype&#xff09;3️⃣ 对象属性&#xff08;Object.defineProperty&#xff09;4️⃣ Proxy5️⃣ 批量hook示例&#x1f6ec; 文章小结&#x1f4d6; 参考资料&#x1f6eb; 导读 需求 …

【面试题系列】K8S常见面试题

目录 序言 问题 1. 简单说一下k8s集群内外网络如何互通的吧 2.描述一下pod的创建过程 3. 描述一下k8s pod的终止过程 4.Kubernetes 中的自动伸缩有哪些方式&#xff1f; 5.Kubernetes 中的故障检测有哪些方式&#xff1f; 6.Kubernetes 中的资源调度有哪些方式&#xff…

如何优雅的用POI导入Excel文件

在企业级项目开发中&#xff0c;要经常涉及excel文件和程序之间导入导出的业务要求&#xff0c;那么今天来讲一讲excel文件导入的实现。java实现对excel的操作有很多种方式&#xff0c;例如EasyExcel等&#xff0c;今天我们使用的是POI技术实现excel文件的导入。POI技术简介1.P…

全连接神经网络

目录 1.全连接神经网络简介 2.MLP分类模型 2.1 数据准备与探索 2.2 搭建网络并可视化 2.3 使用未预处理的数据训练模型 2.4 使用预处理后的数据进行模型训练 3. MLP回归模型 3.1 数据准备 3.2 搭建回归预测网络 1.全连接神经网络简介 全连接神经网络(Multi-Layer Percep…

基于Vue3和element-plus实现一个完整的登录功能

先看一下最终要实现的效果:登录页面:注册页面:(1)引入element-plus组件库引入组件库的方式有好多种,在这里我就在main.js全局引入了.npm i element-plus -Smain.js中代码:import { createApp } from "vue"; //element-plus import ElementPlus from "element-pl…

双指针 -876. 链表的中间结点-leetcode

开始一个专栏&#xff0c;写自己的博客 双指针&#xff0c;也算是作为自己的笔记吧&#xff01; 双指针从广义上来说&#xff0c;是指用两个变量在线性结构上遍历而解决的问题。狭义上说&#xff0c; 对于数组&#xff0c;指两个变量在数组上相向移动解决的问题&#xff1b;对…

「SAP ABAP」OPEN SQL(四)【FROM语句】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

女子举重问题

一、问题的描述 问题及要求 1、搜集各个级别世界女子举重比赛的实际数据。分别建立女子举重比赛总成绩的线性模型、幂函数模型、幂函数改进模型&#xff0c;并最终建立总冠军评选模型。 应用以上模型对最近举行的一届奥运会女子举重比赛总成绩进行排名&#xff0c;并对模型及…

【2023-03-10】JS逆向之美团滑块

提示&#xff1a;文章仅供参考&#xff0c;禁止用于非法途径 前言 目标网站:aHR0cHM6Ly9wYXNzcG9ydC5tZWl0dWFuLmNvbS9hY2NvdW50L3VuaXRpdmVsb2dpbg 页面分析 接口流程 1.https://passport.meituan.com/account/unitivelogin主页接口&#xff1a;需获取下面的参数&#xff0…

力扣刷题---初始链表1

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言初阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:讲解初始数据结构链表的三个力扣题 1.移除链表元素. 2.反转…

Visual Studio Code 1.76 发布

欢迎使用 Visual Studio Code 2023 年 2 月版&#xff0c;其中一些亮点包括&#xff1a; 配置文件 - 活动配置文件徽章&#xff0c;通过命令面板快速切换配置文件。辅助功能改进 - 新的音频提示&#xff0c;改进的终端屏幕阅读器模式。可移动的 Explorer 视图- 将资源管理器放…

JavaWeb——Request(请求)和Response(响应)介绍

在写servlet时需要实现5个方法&#xff0c;在一个service方法里面有两个参数request和response。 浏览器向服务器发送请求会发送HTTP的请求数据——字符串&#xff0c;这些字符串会被Tomcat所解析&#xff0c;然后这些请求数据会被放到一个对象(request)里面保存。 相应的Tom…

有图解有案例,我终于把 Condition 的原理讲透彻了

哈喽大家好&#xff0c;我是阿Q&#xff01; 20张图图解ReentrantLock加锁解锁原理文章一发&#xff0c;便引发了大家激烈的讨论&#xff0c;更有小伙伴前来弹窗&#xff1a;平时加解锁都是直接使用Synchronized关键字来实现的&#xff0c;简单好用&#xff0c;为啥还要引用Re…

React面向组件编程(理解与使用+state+props+refs与事件处理)

1 基本理解与使用 函数式组件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"…

开发板与ubantu文件传送

接下来的所以实验都通过下面这种方式发送APP文件到开发板运行 目录 1、在ubantu配置 ①在虚拟机上添加一个桥接模式的虚拟网卡 ②设定网卡 ③在网卡上配置静态地址 2、开发板设置 ①查看网卡 ②配置网卡静态ip 3、 测试 ①ping ②文件传送 传送报错情况 配置环境&#…

Java Web 实战 14 - 计算机网络之初识计算机网络

初识计算机网络一 . 网络发展史二 . 局域网 VS 广域网2.1 交换机与路由器2.2 集线器三 . 网络通信基础3.1 协议3.1.1 OSI 七层模型3.1.2 TCP / IP 五层模型3.2 交换机和路由器的区别3.3 封装和分用大家好 , 这篇文章给大家分享的是计算机网络的一些基础知识 , 我们会给大家分享…

钉钉,下沉进农田

在这个古老的产业里&#xff0c;数字化没有被放到更高的位置&#xff0c;但难点依旧存在。钉钉恰是基于它足够柔性的产品特性和普惠的服务模式&#xff0c;真正帮助农食产业中的人和企业解决着过去一直没有解决的问题&#xff0c;让这个产业中的人和环节都向数字化潮水迈进了一…

linux目录——文件管理

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。个人主页&#xff1a;小李会科技的…
最新文章