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

C#中的设计模式与重构技巧

2022-11-155.5k 阅读

设计模式基础概念

在软件开发中,设计模式是针对反复出现的问题所总结出的通用解决方案。这些模式经过实践验证,能够帮助开发者构建更灵活、可维护且可扩展的软件系统。在C#语言环境下,掌握设计模式对于提升代码质量和开发效率至关重要。

设计模式的分类

设计模式通常分为三大类:创建型模式、结构型模式和行为型模式。

创建型模式

创建型模式主要用于对象的创建过程,它隐藏了对象创建的具体细节,让代码更加简洁和可维护。例如单例模式,在整个应用程序生命周期内,保证一个类只有一个实例,并提供一个全局访问点。以下是C#实现单例模式的代码示例:

public class Singleton
{
    private static Singleton instance;
    private static readonly object lockObject = new object();

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (lockObject)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

在上述代码中,Singleton类的构造函数被设为私有,防止外部直接实例化。通过Instance属性来获取唯一的实例,使用双重检查锁定机制确保在多线程环境下也能正确创建唯一实例。

结构型模式

结构型模式关注如何将类或对象组合成更大的结构,以实现新的功能或优化现有结构。比如代理模式,为其他对象提供一种代理以控制对这个对象的访问。假设我们有一个远程服务类RemoteService,由于网络等原因,直接访问可能存在性能问题,这时可以使用代理模式。代码示例如下:

public interface IRemoteService
{
    void DoWork();
}

public class RemoteService : IRemoteService
{
    public void DoWork()
    {
        Console.WriteLine("Remote service is doing work...");
    }
}

public class RemoteServiceProxy : IRemoteService
{
    private RemoteService remoteService;

    public void DoWork()
    {
        if (remoteService == null)
        {
            remoteService = new RemoteService();
        }
        // 可以在调用前添加一些预处理逻辑,如权限检查等
        remoteService.DoWork();
        // 可以在调用后添加一些后处理逻辑,如记录日志等
    }
}

在上述代码中,RemoteServiceProxy类代理了RemoteService类,通过实现相同的接口IRemoteService,控制对RemoteService的访问。

行为型模式

行为型模式主要用于处理对象之间的交互和职责分配,以实现复杂的行为。以观察者模式为例,它定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。假设我们有一个气象站,气象数据发生变化时,需要通知多个显示设备。代码如下:

public interface IObserver
{
    void Update(float temperature, float humidity, float pressure);
}

public interface ISubject
{
    void RegisterObserver(IObserver o);
    void RemoveObserver(IObserver o);
    void NotifyObservers();
}

public class WeatherData : ISubject
{
    private List<IObserver> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData()
    {
        observers = new List<IObserver>();
    }

    public void RegisterObserver(IObserver o)
    {
        observers.Add(o);
    }

    public void RemoveObserver(IObserver o)
    {
        observers.Remove(o);
    }

    public void NotifyObservers()
    {
        foreach (var observer in observers)
        {
            observer.Update(temperature, humidity, pressure);
        }
    }

    public void MeasurementsChanged()
    {
        NotifyObservers();
    }

    public void SetMeasurements(float temperature, float humidity, float pressure)
    {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        MeasurementsChanged();
    }
}

public class CurrentConditionsDisplay : IObserver
{
    private float temperature;
    private float humidity;
    private ISubject weatherData;

    public CurrentConditionsDisplay(ISubject weatherData)
    {
        this.weatherData = weatherData;
        weatherData.RegisterObserver(this);
    }

    public void Update(float temperature, float humidity, float pressure)
    {
        this.temperature = temperature;
        this.humidity = humidity;
        Display();
    }

    public void Display()
    {
        Console.WriteLine($"Current conditions: {temperature}F degrees and {humidity}% humidity");
    }
}

在上述代码中,WeatherData类作为主题,实现了ISubject接口,管理观察者列表并在数据变化时通知观察者。CurrentConditionsDisplay类作为观察者,实现了IObserver接口,接收主题通知并更新显示。

C#中的常用设计模式实践

工厂模式

工厂模式是创建型模式的一种,它将对象的创建和使用分离。工厂模式又分为简单工厂、工厂方法和抽象工厂。

简单工厂模式

简单工厂模式定义了一个工厂类,用于创建产品对象。假设我们有一个图形绘制系统,需要绘制不同类型的图形,如圆形和矩形。代码示例如下:

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

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

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

public class ShapeFactory
{
    public static Shape CreateShape(string shapeType)
    {
        if (shapeType.Equals("circle", StringComparison.OrdinalIgnoreCase))
        {
            return new Circle();
        }
        else if (shapeType.Equals("rectangle", StringComparison.OrdinalIgnoreCase))
        {
            return new Rectangle();
        }
        return null;
    }
}

在上述代码中,ShapeFactory类的CreateShape方法根据传入的参数创建不同类型的Shape对象。使用时,代码如下:

class Program
{
    static void Main()
    {
        Shape circle = ShapeFactory.CreateShape("circle");
        if (circle != null)
        {
            circle.Draw();
        }
        Shape rectangle = ShapeFactory.CreateShape("rectangle");
        if (rectangle != null)
        {
            rectangle.Draw();
        }
    }
}

工厂方法模式

工厂方法模式将对象的创建延迟到子类。还是以图形绘制系统为例,代码示例如下:

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

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

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

public abstract class ShapeFactory
{
    public abstract Shape CreateShape();
}

public class CircleFactory : ShapeFactory
{
    public override Shape CreateShape()
    {
        return new Circle();
    }
}

public class RectangleFactory : ShapeFactory
{
    public override Shape CreateShape()
    {
        return new Rectangle();
    }
}

使用时,代码如下:

class Program
{
    static void Main()
    {
        ShapeFactory circleFactory = new CircleFactory();
        Shape circle = circleFactory.CreateShape();
        if (circle != null)
        {
            circle.Draw();
        }
        ShapeFactory rectangleFactory = new RectangleFactory();
        Shape rectangle = rectangleFactory.CreateShape();
        if (rectangle != null)
        {
            rectangle.Draw();
        }
    }
}

在工厂方法模式中,每个具体的产品创建由具体的工厂子类负责,这样使得系统更加灵活,易于扩展。

抽象工厂模式

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。假设我们有一个游戏开发场景,需要创建不同类型的游戏角色(战士、法师)和武器(剑、法杖),并且这些角色和武器需要配套。代码示例如下:

public interface ICharacter
{
    void Display();
}

public interface IWeapon
{
    void Use();
}

public class Warrior : ICharacter
{
    public void Display()
    {
        Console.WriteLine("A warrior appears.");
    }
}

public class Mage : ICharacter
{
    public void Display()
    {
        Console.WriteLine("A mage appears.");
    }
}

public class Sword : IWeapon
{
    public void Use()
    {
        Console.WriteLine("The warrior uses a sword.");
    }
}

public class Staff : IWeapon
{
    public void Use()
    {
        Console.WriteLine("The mage uses a staff.");
    }
}

public abstract class GameFactory
{
    public abstract ICharacter CreateCharacter();
    public abstract IWeapon CreateWeapon();
}

public class WarriorFactory : GameFactory
{
    public override ICharacter CreateCharacter()
    {
        return new Warrior();
    }

    public override IWeapon CreateWeapon()
    {
        return new Sword();
    }
}

public class MageFactory : GameFactory
{
    public override ICharacter CreateCharacter()
    {
        return new Mage();
    }

    public override IWeapon CreateWeapon()
    {
        return new Staff();
    }
}

使用时,代码如下:

class Program
{
    static void Main()
    {
        GameFactory warriorFactory = new WarriorFactory();
        ICharacter warrior = warriorFactory.CreateCharacter();
        IWeapon sword = warriorFactory.CreateWeapon();
        warrior.Display();
        sword.Use();

        GameFactory mageFactory = new MageFactory();
        ICharacter mage = mageFactory.CreateCharacter();
        IWeapon staff = mageFactory.CreateWeapon();
        mage.Display();
        staff.Use();
    }
}

抽象工厂模式使得创建相关对象的过程更加统一和可维护,适合创建对象之间存在依赖关系的场景。

装饰器模式

装饰器模式属于结构型模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。以咖啡店为例,咖啡可以添加不同的配料(如牛奶、糖),每添加一种配料就增加一种功能。代码示例如下:

public abstract class Coffee
{
    public string Description { get; set; } = "Unknown coffee";

    public virtual double Cost()
    {
        return 0;
    }
}

public class SimpleCoffee : Coffee
{
    public SimpleCoffee()
    {
        Description = "Simple coffee";
    }

    public override double Cost()
    {
        return 2.0;
    }
}

public abstract class CoffeeDecorator : Coffee
{
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee decoratedCoffee)
    {
        this.decoratedCoffee = decoratedCoffee;
    }

    public override string Description => decoratedCoffee.Description;

    public override double Cost()
    {
        return decoratedCoffee.Cost();
    }
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(Coffee decoratedCoffee) : base(decoratedCoffee)
    {
        Description += ", with milk";
    }

    public override double Cost()
    {
        return decoratedCoffee.Cost() + 0.5;
    }
}

public class SugarDecorator : CoffeeDecorator
{
    public SugarDecorator(Coffee decoratedCoffee) : base(decoratedCoffee)
    {
        Description += ", with sugar";
    }

    public override double Cost()
    {
        return decoratedCoffee.Cost() + 0.2;
    }
}

使用时,代码如下:

class Program
{
    static void Main()
    {
        Coffee simpleCoffee = new SimpleCoffee();
        Console.WriteLine($"{simpleCoffee.Description}: ${simpleCoffee.Cost()}");

        Coffee coffeeWithMilk = new MilkDecorator(simpleCoffee);
        Console.WriteLine($"{coffeeWithMilk.Description}: ${coffeeWithMilk.Cost()}");

        Coffee coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
        Console.WriteLine($"{coffeeWithMilkAndSugar.Description}: ${coffeeWithMilkAndSugar.Cost()}");
    }
}

在上述代码中,CoffeeDecorator及其子类通过包装Coffee对象,动态地添加新的功能,如添加牛奶或糖,而不需要修改Coffee类的原有结构。

策略模式

策略模式是行为型模式,它定义了一系列算法,将每个算法封装起来,并且使它们可以相互替换。以一个电商系统的运费计算为例,不同的运输方式有不同的运费计算策略。代码示例如下:

public interface IShippingStrategy
{
    double CalculateShippingCost(double weight, double distance);
}

public class ExpressShipping : IShippingStrategy
{
    public double CalculateShippingCost(double weight, double distance)
    {
        return weight * distance * 0.1;
    }
}

public class StandardShipping : IShippingStrategy
{
    public double CalculateShippingCost(double weight, double distance)
    {
        return weight * distance * 0.05;
    }
}

public class Order
{
    public double Weight { get; set; }
    public double Distance { get; set; }
    private IShippingStrategy shippingStrategy;

    public Order(double weight, double distance, IShippingStrategy shippingStrategy)
    {
        this.Weight = weight;
        this.Distance = distance;
        this.shippingStrategy = shippingStrategy;
    }

    public double CalculateShippingCost()
    {
        return shippingStrategy.CalculateShippingCost(Weight, Distance);
    }
}

使用时,代码如下:

class Program
{
    static void Main()
    {
        IShippingStrategy expressShipping = new ExpressShipping();
        Order order1 = new Order(10, 50, expressShipping);
        Console.WriteLine($"Express shipping cost: ${order1.CalculateShippingCost()}");

        IShippingStrategy standardShipping = new StandardShipping();
        Order order2 = new Order(10, 50, standardShipping);
        Console.WriteLine($"Standard shipping cost: ${order2.CalculateShippingCost()}");
    }
}

在上述代码中,Order类通过持有不同的IShippingStrategy对象,实现了不同的运费计算策略,使得运费计算方式可以灵活切换。

重构技巧概述

重构是对现有代码进行优化,在不改变代码外部行为的前提下,改善其内部结构、提高可读性、增强可维护性和可扩展性。在C#开发中,有许多实用的重构技巧。

提取方法

当一个方法中包含过多的代码逻辑,导致方法过长且难以理解时,可以使用提取方法重构技巧。例如,我们有一个处理用户注册的方法,其中包含了验证邮箱格式、检查用户名是否存在以及创建用户等多个逻辑。

public class UserService
{
    public void RegisterUser(string username, string email, string password)
    {
        // 验证邮箱格式
        if (!Regex.IsMatch(email, @"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"))
        {
            throw new ArgumentException("Invalid email format");
        }
        // 检查用户名是否存在
        if (IsUsernameExists(username))
        {
            throw new ArgumentException("Username already exists");
        }
        // 创建用户
        CreateUser(username, email, password);
    }

    private bool IsUsernameExists(string username)
    {
        // 实际逻辑:查询数据库判断用户名是否存在
        return false;
    }

    private void CreateUser(string username, string email, string password)
    {
        // 实际逻辑:在数据库中创建用户记录
    }
}

上述RegisterUser方法逻辑复杂且冗长,我们可以将验证邮箱格式和检查用户名是否存在的逻辑提取成单独的方法。

public class UserService
{
    public void RegisterUser(string username, string email, string password)
    {
        ValidateEmail(email);
        ValidateUsername(username);
        CreateUser(username, email, password);
    }

    private void ValidateEmail(string email)
    {
        if (!Regex.IsMatch(email, @"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"))
        {
            throw new ArgumentException("Invalid email format");
        }
    }

    private void ValidateUsername(string username)
    {
        if (IsUsernameExists(username))
        {
            throw new ArgumentException("Username already exists");
        }
    }

    private bool IsUsernameExists(string username)
    {
        // 实际逻辑:查询数据库判断用户名是否存在
        return false;
    }

    private void CreateUser(string username, string email, string password)
    {
        // 实际逻辑:在数据库中创建用户记录
    }
}

这样重构后,RegisterUser方法更加清晰,每个提取出来的方法职责单一,易于理解和维护。

内联方法

与提取方法相反,当一个方法的逻辑非常简单,调用它的开销大于其本身的执行开销时,可以使用内联方法重构技巧。例如,我们有一个简单的获取用户年龄的方法。

public class User
{
    public int BirthYear { get; set; }

    public int GetAge(int currentYear)
    {
        return currentYear - BirthYear;
    }
}

如果在很多地方调用GetAge方法,且这个方法的逻辑非常简单,我们可以将其内联。

public class User
{
    public int BirthYear { get; set; }
}

class Program
{
    static void Main()
    {
        User user = new User { BirthYear = 1990 };
        int currentYear = 2023;
        int age = currentYear - user.BirthYear;
        Console.WriteLine($"User's age is {age}");
    }
}

内联方法后,减少了方法调用的开销,使代码更加简洁。

重构重复代码

重复代码是代码质量的大敌,它增加了维护成本和出错的可能性。例如,在一个电商系统中,订单处理和库存管理模块都有计算商品总价的逻辑。

public class Order
{
    public List<Product> Products { get; set; }

    public decimal CalculateTotalPrice()
    {
        decimal total = 0;
        foreach (var product in Products)
        {
            total += product.Price * product.Quantity;
        }
        return total;
    }
}

public class Inventory
{
    public List<Product> Products { get; set; }

    public decimal CalculateTotalValue()
    {
        decimal total = 0;
        foreach (var product in Products)
        {
            total += product.Price * product.Quantity;
        }
        return total;
    }
}

public class Product
{
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

可以将计算总价的逻辑提取到一个公共方法中。

public static class ProductExtensions
{
    public static decimal CalculateTotal(this List<Product> products)
    {
        decimal total = 0;
        foreach (var product in products)
        {
            total += product.Price * product.Quantity;
        }
        return total;
    }
}

public class Order
{
    public List<Product> Products { get; set; }

    public decimal CalculateTotalPrice()
    {
        return Products.CalculateTotal();
    }
}

public class Inventory
{
    public List<Product> Products { get; set; }

    public decimal CalculateTotalValue()
    {
        return Products.CalculateTotal();
    }
}

public class Product
{
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

通过这种方式,消除了重复代码,提高了代码的可维护性。

使用设计模式进行重构

在重构过程中,合理运用设计模式可以有效提升代码的质量。例如,当一个类的功能不断增加,导致类变得臃肿时,可以考虑使用装饰器模式进行重构。假设我们有一个TextBox类,最初只有基本的文本输入功能,随着需求增加,需要添加密码掩码、水印等功能。

public class TextBox
{
    private string text;
    private bool isPassword;
    private string watermark;

    public void SetText(string text)
    {
        this.text = text;
    }

    public string GetText()
    {
        if (isPassword)
        {
            return new string('*', text.Length);
        }
        return text;
    }

    public void SetIsPassword(bool isPassword)
    {
        this.isPassword = isPassword;
    }

    public void SetWatermark(string watermark)
    {
        this.watermark = watermark;
    }

    public string GetWatermark()
    {
        return watermark;
    }
}

上述TextBox类随着功能增加,变得越来越复杂。使用装饰器模式重构如下:

public abstract class TextBoxComponent
{
    public abstract string GetText();
    public virtual string GetWatermark()
    {
        return "";
    }
}

public class BasicTextBox : TextBoxComponent
{
    private string text;

    public void SetText(string text)
    {
        this.text = text;
    }

    public override string GetText()
    {
        return text;
    }
}

public abstract class TextBoxDecorator : TextBoxComponent
{
    protected TextBoxComponent textBoxComponent;

    public TextBoxDecorator(TextBoxComponent textBoxComponent)
    {
        this.textBoxComponent = textBoxComponent;
    }

    public override string GetText()
    {
        return textBoxComponent.GetText();
    }

    public override string GetWatermark()
    {
        return textBoxComponent.GetWatermark();
    }
}

public class PasswordTextBoxDecorator : TextBoxDecorator
{
    public PasswordTextBoxDecorator(TextBoxComponent textBoxComponent) : base(textBoxComponent)
    {
    }

    public override string GetText()
    {
        return new string('*', textBoxComponent.GetText().Length);
    }
}

public class WatermarkTextBoxDecorator : TextBoxDecorator
{
    private string watermark;

    public WatermarkTextBoxDecorator(TextBoxComponent textBoxComponent, string watermark) : base(textBoxComponent)
    {
        this.watermark = watermark;
    }

    public override string GetWatermark()
    {
        return watermark;
    }
}

使用时,代码如下:

class Program
{
    static void Main()
    {
        BasicTextBox basicTextBox = new BasicTextBox();
        basicTextBox.SetText("Hello");

        PasswordTextBoxDecorator passwordTextBox = new PasswordTextBoxDecorator(basicTextBox);
        Console.WriteLine($"Password text: {passwordTextBox.GetText()}");

        WatermarkTextBoxDecorator watermarkTextBox = new WatermarkTextBoxDecorator(passwordTextBox, "Enter password");
        Console.WriteLine($"Watermark: {watermarkTextBox.GetWatermark()}");
    }
}

通过使用装饰器模式重构,TextBox相关功能变得更加灵活和可扩展,每个装饰器负责单一功能,提高了代码的可维护性。

在C#开发中,深入理解设计模式和重构技巧,能够帮助开发者构建高质量、可维护且易于扩展的软件系统,提升开发效率和代码质量。在实际项目中,应根据具体需求和场景,灵活运用这些知识,不断优化代码。