MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#代码重构技巧与设计模式应用场景

2022-05-026.3k 阅读

C#代码重构技巧

提炼方法(Extract Method)

在C#编程中,提炼方法是一种极为基础且常用的重构技巧。当一个方法变得冗长、复杂,包含多个不同职责的代码块时,将这些代码块分别提炼成独立的方法,能显著提高代码的可读性和可维护性。

假设我们有如下一段计算订单总价并打印订单信息的代码:

public class Order
{
    public List<Product> Products { get; set; }
    public double CalculateTotalPrice()
    {
        double total = 0;
        foreach (var product in Products)
        {
            total += product.Price * product.Quantity;
        }
        Console.WriteLine("Order details:");
        foreach (var product in Products)
        {
            Console.WriteLine($"Product: {product.Name}, Price: {product.Price}, Quantity: {product.Quantity}");
        }
        Console.WriteLine($"Total Price: {total}");
        return total;
    }
}

public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
    public int Quantity { get; set; }
}

CalculateTotalPrice方法中,既包含了计算总价的逻辑,又包含了打印订单信息的逻辑。我们可以将计算总价和打印订单信息的逻辑分别提炼成独立的方法:

public class Order
{
    public List<Product> Products { get; set; }
    public double CalculateTotalPrice()
    {
        double total = CalculateTotal();
        PrintOrderDetails(total);
        return total;
    }
    private double CalculateTotal()
    {
        double total = 0;
        foreach (var product in Products)
        {
            total += product.Price * product.Quantity;
        }
        return total;
    }
    private void PrintOrderDetails(double total)
    {
        Console.WriteLine("Order details:");
        foreach (var product in Products)
        {
            Console.WriteLine($"Product: {product.Name}, Price: {product.Price}, Quantity: {product.Quantity}");
        }
        Console.WriteLine($"Total Price: {total}");
    }
}

public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
    public int Quantity { get; set; }
}

这样,CalculateTotalPrice方法变得更加清晰,每个提炼出来的方法职责单一,便于理解和维护。

内联方法(Inline Method)

与提炼方法相反,当一个方法的逻辑过于简单,其存在反而增加了代码的复杂性和阅读成本时,可以使用内联方法重构技巧。例如,有这样一个简单的方法:

public class MathUtils
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

如果在调用处,Add方法的使用场景非常简单,比如:

MathUtils utils = new MathUtils();
int result = utils.Add(3, 5);

此时可以将Add方法内联,直接写成:

int result = 3 + 5;

这样可以减少方法调用的开销,并且使代码更加直接明了。但需要注意的是,如果该方法在多个地方被调用,内联可能会导致代码重复,需要权衡利弊。

替换临时变量(Replace Temp with Query)

在C#代码中,临时变量常常用于存储中间计算结果。然而,过多的临时变量会使代码变得混乱,尤其是当它们在方法中被多次赋值时。通过替换临时变量为查询方法,可以使代码更加清晰。

例如,我们有如下计算产品折扣后价格的代码:

public class Product
{
    public double Price { get; set; }
    public double Discount { get; set; }
    public double CalculateDiscountedPrice()
    {
        double discountAmount = Price * Discount;
        double discountedPrice = Price - discountAmount;
        return discountedPrice;
    }
}

可以将临时变量替换为查询方法:

public class Product
{
    public double Price { get; set; }
    public double Discount { get; set; }
    public double CalculateDiscountedPrice()
    {
        return Price - CalculateDiscountAmount();
    }
    private double CalculateDiscountAmount()
    {
        return Price * Discount;
    }
}

这样,CalculateDiscountedPrice方法的逻辑更加清晰,并且CalculateDiscountAmount方法可以在其他需要计算折扣金额的地方复用。

移除死代码(Remove Dead Code)

死代码是指永远不会被执行的代码,可能是因为条件永远不成立,或者是方法不再被调用等原因。移除死代码能使代码库更加简洁,减少维护负担。

例如,有如下代码:

public class SomeClass
{
    public void SomeMethod()
    {
        bool flag = false;
        if (flag)
        {
            Console.WriteLine("This is dead code");
        }
        // 下面这个方法已经不再被其他地方调用
        UnusedMethod();
    }
    private void UnusedMethod()
    {
        Console.WriteLine("This method is not used anymore");
    }
}

我们应该删除if (flag)块内的代码以及UnusedMethod方法,使代码更加简洁:

public class SomeClass
{
    public void SomeMethod()
    {
        // 代码变得简洁,无死代码
    }
}

重构条件逻辑(Refactoring Conditional Logic)

复杂的条件逻辑会使代码难以理解和维护。可以通过多种方式重构条件逻辑,使其更加清晰。

分解条件表达式(Decompose Conditional)

当一个条件表达式包含多个逻辑判断,并且每个判断都有不同的处理逻辑时,可以将其分解为多个简单的条件表达式。

例如,有如下代码:

public class OrderProcessor
{
    public void ProcessOrder(Order order)
    {
        if (order.IsPriority && order.TotalPrice > 100 && order.Products.Count > 5)
        {
            Console.WriteLine("Process as high - priority order");
            // 高优先级订单处理逻辑
        }
        else
        {
            Console.WriteLine("Process as normal order");
            // 普通订单处理逻辑
        }
    }
}

可以分解为:

public class OrderProcessor
{
    public void ProcessOrder(Order order)
    {
        if (IsHighPriorityOrder(order))
        {
            Console.WriteLine("Process as high - priority order");
            // 高优先级订单处理逻辑
        }
        else
        {
            Console.WriteLine("Process as normal order");
            // 普通订单处理逻辑
        }
    }
    private bool IsHighPriorityOrder(Order order)
    {
        return order.IsPriority && order.TotalPrice > 100 && order.Products.Count > 5;
    }
}

这样,ProcessOrder方法的逻辑更加清晰,IsHighPriorityOrder方法也可以在其他需要判断高优先级订单的地方复用。

以多态取代条件表达式(Replace Conditional with Polymorphism)

当条件表达式根据对象的类型来执行不同的逻辑时,可以使用多态来代替条件表达式,以实现更加灵活和可维护的代码。

假设有一个根据不同形状计算面积的程序:

public class Shape
{
    public enum ShapeType { Circle, Rectangle }
    public ShapeType Type { get; set; }
    public double Radius { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
    public double CalculateArea()
    {
        if (Type == ShapeType.Circle)
        {
            return Math.PI * Radius * Radius;
        }
        else if (Type == ShapeType.Rectangle)
        {
            return Width * Height;
        }
        return 0;
    }
}

可以通过多态重构为:

public abstract class Shape
{
    public abstract double CalculateArea();
}

public class Circle : Shape
{
    public double Radius { get; set; }
    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public override double CalculateArea()
    {
        return Width * Height;
    }
}

这样,在使用时,只需要根据实际的形状类型调用CalculateArea方法,而不需要复杂的条件判断:

Shape circle = new Circle { Radius = 5 };
double circleArea = circle.CalculateArea();
Shape rectangle = new Rectangle { Width = 4, Height = 6 };
double rectangleArea = rectangle.CalculateArea();

设计模式应用场景

单例模式(Singleton Pattern)

单例模式确保一个类只有一个实例,并提供一个全局访问点。在C#中,单例模式常用于需要全局唯一实例的场景,比如数据库连接池、日志记录器等。

实现方式

  1. 饿汉式单例:在类加载时就创建实例。
public sealed class Logger
{
    private static readonly Logger instance = new Logger();
    private Logger() { }
    public static Logger Instance
    {
        get
        {
            return instance;
        }
    }
    public void LogMessage(string message)
    {
        Console.WriteLine($"Logging: {message}");
    }
}
  1. 懒汉式单例:在第一次使用时创建实例。
public sealed class DatabaseConnectionPool
{
    private static DatabaseConnectionPool instance;
    private static readonly object padlock = new object();
    private DatabaseConnectionPool() { }
    public static DatabaseConnectionPool Instance
    {
        get
        {
            if (instance == null)
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = new DatabaseConnectionPool();
                    }
                }
            }
            return instance;
        }
    }
    // 数据库连接池相关方法
}

应用场景

  • 数据库连接池:在应用程序中,数据库连接是宝贵的资源。使用单例模式创建数据库连接池,可以确保整个应用程序共享一个连接池,避免频繁创建和销毁连接带来的性能开销。
  • 日志记录器:在整个应用程序中,可能需要一个统一的日志记录器来记录各种事件。单例模式的日志记录器可以保证所有的日志都被记录到同一个地方,便于管理和分析。

工厂模式(Factory Pattern)

工厂模式提供了一种创建对象的方式,将对象的创建和使用分离。在C#中,工厂模式常用于创建对象的过程比较复杂,或者需要根据不同条件创建不同类型对象的场景。

简单工厂模式(Simple Factory Pattern)

简单工厂模式定义了一个工厂类,用于创建产品对象。

假设有一个创建不同类型汽车的场景:

public abstract class Car
{
    public abstract void Drive();
}

public class Sedan : Car
{
    public override void Drive()
    {
        Console.WriteLine("Driving a sedan");
    }
}

public class SUV : Car
{
    public override void Drive()
    {
        Console.WriteLine("Driving an SUV");
    }
}

public class CarFactory
{
    public static Car CreateCar(string carType)
    {
        switch (carType.ToLower())
        {
            case "sedan":
                return new Sedan();
            case "suv":
                return new SUV();
            default:
                return null;
        }
    }
}

使用时:

Car sedan = CarFactory.CreateCar("sedan");
sedan.Drive();

工厂方法模式(Factory Method Pattern)

工厂方法模式将对象的创建延迟到子类。

public abstract class CarFactory
{
    public abstract Car CreateCar();
}

public class SedanFactory : CarFactory
{
    public override Car CreateCar()
    {
        return new Sedan();
    }
}

public class SUVFactory : CarFactory
{
    public override Car CreateCar()
    {
        return new SUV();
    }
}

使用时:

CarFactory sedanFactory = new SedanFactory();
Car sedan = sedanFactory.CreateCar();
sedan.Drive();

抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

假设汽车除了本身,还需要创建相应的轮胎:

public abstract class Car
{
    public abstract void Drive();
}

public class Sedan : Car
{
    public override void Drive()
    {
        Console.WriteLine("Driving a sedan");
    }
}

public class SUV : Car
{
    public override void Drive()
    {
        Console.WriteLine("Driving an SUV");
    }
}

public abstract class Tire
{
    public abstract void Roll();
}

public class SedanTire : Tire
{
    public override void Roll()
    {
        Console.WriteLine("Sedan tire is rolling");
    }
}

public class SUVTire : Tire
{
    public override void Roll()
    {
        Console.WriteLine("SUV tire is rolling");
    }
}

public abstract class CarFactory
{
    public abstract Car CreateCar();
    public abstract Tire CreateTire();
}

public class SedanFactory : CarFactory
{
    public override Car CreateCar()
    {
        return new Sedan();
    }
    public override Tire CreateTire()
    {
        return new SedanTire();
    }
}

public class SUVFactory : CarFactory
{
    public override Car CreateCar()
    {
        return new SUV();
    }
    public override Tire CreateTire()
    {
        return new SUVTire();
    }
}

使用时:

CarFactory sedanFactory = new SedanFactory();
Car sedan = sedanFactory.CreateCar();
Tire sedanTire = sedanFactory.CreateTire();
sedan.Drive();
sedanTire.Roll();

应用场景

  • 对象创建复杂:当创建对象需要进行复杂的初始化操作,如从配置文件读取参数、进行数据库查询等,使用工厂模式可以将这些复杂的操作封装在工厂类中,使客户端代码更加简洁。
  • 根据不同条件创建不同类型对象:在游戏开发中,根据玩家的选择创建不同类型的角色,或者在图形绘制中,根据用户选择绘制不同的图形,都可以使用工厂模式。

策略模式(Strategy Pattern)

策略模式定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。在C#中,策略模式常用于需要根据不同情况选择不同算法或行为的场景。

假设有一个计算订单运费的场景,根据不同的运输方式有不同的运费计算策略:

public interface IShippingStrategy
{
    double CalculateShippingCost(double orderTotal);
}

public class ExpressShipping : IShippingStrategy
{
    public double CalculateShippingCost(double orderTotal)
    {
        if (orderTotal > 100)
        {
            return 10;
        }
        return 20;
    }
}

public class StandardShipping : IShippingStrategy
{
    public double CalculateShippingCost(double orderTotal)
    {
        if (orderTotal > 50)
        {
            return 5;
        }
        return 10;
    }
}

public class Order
{
    public double TotalPrice { get; set; }
    public IShippingStrategy ShippingStrategy { get; set; }
    public double CalculateShippingCost()
    {
        return ShippingStrategy.CalculateShippingCost(TotalPrice);
    }
}

使用时:

Order order = new Order { TotalPrice = 120 };
order.ShippingStrategy = new ExpressShipping();
double shippingCost = order.CalculateShippingCost();

应用场景

  • 算法切换:在图像处理中,可能有不同的图像压缩算法,如JPEG、PNG等。使用策略模式可以根据用户需求或图像特点动态切换压缩算法。
  • 行为定制:在电商系统中,不同的促销活动可能有不同的折扣计算策略,使用策略模式可以方便地实现这些不同策略的切换和定制。

观察者模式(Observer Pattern)

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新。在C#中,观察者模式常用于实现事件驱动的系统。

假设有一个股票价格变化通知的场景:

using System;
using System.Collections.Generic;

public class Stock
{
    private string symbol;
    private double price;
    private List<IInvestor> investors = new List<IInvestor>();

    public Stock(string symbol, double price)
    {
        this.symbol = symbol;
        this.price = price;
    }

    public double Price
    {
        get { return price; }
        set
        {
            if (price != value)
            {
                price = value;
                NotifyInvestors();
            }
        }
    }

    public void Attach(IInvestor investor)
    {
        investors.Add(investor);
    }

    public void Detach(IInvestor investor)
    {
        investors.Remove(investor);
    }

    private void NotifyInvestors()
    {
        foreach (var investor in investors)
        {
            investor.Update(this);
        }
    }
}

public interface IInvestor
{
    void Update(Stock stock);
}

public class Investor : IInvestor
{
    private string name;

    public Investor(string name)
    {
        this.name = name;
    }

    public void Update(Stock stock)
    {
        Console.WriteLine($"Notification to {name}: {stock.symbol} price has changed to {stock.Price}");
    }
}

使用时:

Stock stock = new Stock("ABC", 50);
Investor investor1 = new Investor("John");
Investor investor2 = new Investor("Jane");
stock.Attach(investor1);
stock.Attach(investor2);
stock.Price = 55;

应用场景

  • 事件通知:在图形用户界面(GUI)开发中,当用户点击按钮、移动滑块等操作时,需要通知相关的组件进行更新,这可以通过观察者模式实现。
  • 消息推送:在即时通讯系统中,当有新消息到达时,需要推送给所有在线的用户,观察者模式可以很好地实现这一功能。

装饰器模式(Decorator Pattern)

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。在C#中,装饰器模式常用于在运行时给对象添加功能的场景。

假设有一个绘制图形的场景,我们可以给基本图形添加边框、阴影等装饰:

public abstract class Shape
{
    public abstract void Draw();
}

public class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a rectangle");
    }
}

public abstract class ShapeDecorator : Shape
{
    protected Shape decoratedShape;
    public ShapeDecorator(Shape decoratedShape)
    {
        this.decoratedShape = decoratedShape;
    }
    public override void Draw()
    {
        decoratedShape.Draw();
    }
}

public class BorderDecorator : ShapeDecorator
{
    public BorderDecorator(Shape decoratedShape) : base(decoratedShape) { }
    public override void Draw()
    {
        Console.WriteLine("Adding border");
        base.Draw();
        Console.WriteLine("Border added");
    }
}

public class ShadowDecorator : ShapeDecorator
{
    public ShadowDecorator(Shape decoratedShape) : base(decoratedShape) { }
    public override void Draw()
    {
        Console.WriteLine("Adding shadow");
        base.Draw();
        Console.WriteLine("Shadow added");
    }
}

使用时:

Shape rectangle = new Rectangle();
Shape borderedRectangle = new BorderDecorator(rectangle);
Shape borderedAndShadowedRectangle = new ShadowDecorator(borderedRectangle);
borderedAndShadowedRectangle.Draw();

应用场景

  • 动态添加功能:在游戏开发中,角色可能在游戏过程中获得新的技能或装备,这些新的能力可以看作是对角色的装饰。使用装饰器模式可以方便地为角色动态添加这些功能。
  • 功能组合:在文本处理中,可能需要对文本同时应用加粗、斜体、下划线等多种格式,装饰器模式可以实现这些格式的灵活组合。