Skip to content

重构 - 7. 封装

7.1 封装记录(Encapsulate Record)

曾用名以数据类取代记录(Replace Record with Data Class)

重构前

csharp
(string Name, string Country) organization = ("JiaJia's Blog", "China");

重构后

csharp
public Organization Organization { get; } = new Organization(("JiaJia's Blog", "China"));

class Organization
{
    public Organization((string Name, string Country) data)
    {
        Name = data.Name;
        Country = data.Country;
    }

    public string Name { get; set; }
    public string Country { get; set; }
}

操作步骤

由于 C# 中的记录型结构是通过值元组提供的,很多特性和 JavaScript 不同,导致按照书中给定的步骤重构时会一直报错,所以记录一下自己重构的步骤。

  • 创建封装类

    csharp
    class Organization
    {
        public Organization(string name, string country)
        {
            this.Name = name;
            this.Country = country;
        }
    
        public string Name { get; set; }
        public string Country { get; set; }
    }
  • 修改记录为类实例

    csharp
    Organization organization = new Organization("JiaJia's Blog", "China");
  • 右键变量 organization快速操作和重构添加只读修饰符

    csharp
    readonly Organization organization = new Organization("JiaJia's Blog", "China");
  • 右键变量 organization快速操作和重构封装字段:“organization”(并使用属性)

    csharp
    readonly Organization organization = new Organization("JiaJia's Blog", "China");
    
    internal Organization Organization => organization;
  • 右键变量 organization快速操作和重构使用自动属性

    csharp
    internal Organization Organization { get; } = new Organization("JiaJia's Blog", "China");

    个人认为到这一步就可以了,我个人来讲比较倾向于使用属性。如果倾向于使用方法,可以再执行下一步。

  • 右键属性 Organization快速操作和重构将“Organization”替换为方法

    csharp
    private readonly Organization organization = new Organization("JiaJia's Blog", "China");
    
    internal Organization GetOrganization()
    {
        return organization;
    }

    此时调用的地方最终会变成类似如下的形式:

    csharp
    public void Test()
    {
        GetOrganization().Name = "Jiajia's Blog";
        Console.WriteLine($"{GetOrganization().Name}:{GetOrganization().Country}");
    }

7.2 封装集合(Encapsulate Collection)

重构前

csharp
List<Course> courses = new List<Course>();

List<Course> Courses { get => courses; set => courses = value; }

重构后

csharp
List<Course> courses = new List<Course>();

public List<Course> Courses { get => courses.ToList(); }

public void AddCourse(Course course)
{
    courses.Add(course);
}

public void RemoveCourse(Course aCourse)
{
    courses.Remove(aCourse);
}

这里使用了集合的 ToList 扩展方法来返回一个集合的浅拷贝,以避免集合意外的被修改,也方便找到集合的修改点。也可以调用 AsReadOnly 方法返回一个只读的集合。

不过无论是 ToList 还是 AsReadOnly 都只能保护集合的新增和删除,无法避免集合项的属性被修改。

如果一旦添加到集合后属性就不会再被修改,则可以考虑返回集合的深拷贝。此时需要注意调用 RemoveCourse 时必须传递调用 AddCourse 方法时传递的引用。如果传递的是通过 Courses 属性获取的引用,虽然不会报错,但数据其实并没有没删除掉。

关于 C# 中深拷贝的方法和性能可以参考这篇博客,比较推荐使用 JSON 序列化/反序列化的方式。

下面是示例代码,需要安装 Newtonsoft.Json 包。

csharp
public static class CloneExtensions
{
    public static T DeepClone<T>(this T source)
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
    }
}

7.3 以对象取代基本类型(Replace Primitive with Object)

曾用名:以对象取代数据值(Replace Data Value with Object)
曾用名:以类取代类型码(Replace Type Code with Class)

重构前

csharp
orders.Where(m => "high" == m.Priority || "rush" == m.Priority).ToList();

class Order
{
    public string Priority { get; set; }
}

上面示例代码中 Priority 是基本类型 string 型,新建一个 Priority 类型以取代简单的字符串。

重构后

csharp
orders.Where(m => m.Priority.HigherThan(new Priority("normal"))).ToList();

class Order
{
    public Priority Priority { get; set; }
}

class Priority
{
    string _value;

    static List<string> LegalValues { get; } = new List<string> { "low", "normal", "high", "rush" };

    public int Index { 
        get {
            return LegalValues.IndexOf(_value);
        }
    }

    public Priority(string value)
    {
        if (LegalValues.Contains(value))
        {
            _value = value;
        }
        else
        {
            throw new Exception($"{value} 是一个无效的 Priority。");
        }
    }

    public bool Equals(Priority other)
    {
        return this.Index == other.Index;
    }

    public bool HigherThan(Priority other)
    {
        return this.Index > other.Index;
    }

    public bool LowerThan(Priority other)
    {
        return this.Index < other.Index;
    }

    public override string ToString()
    {
        return _value;
    }
}

7.4 以查询取代临时变量(Replace Temp with Query)

重构前

csharp
public double Price
{
    get
    {
        var basePrice = Quantity * ItemPrice;
        if (basePrice > 1000)
        {
            return basePrice * 0.95;
        }
        else
        {
            return basePrice * 0.98;
        }
    }
}

重构后

csharp
public double Price
{
    get
    {
        if (BasePrice > 1000)
        {
            return BasePrice * 0.95;
        }
        else
        {
            return BasePrice * 0.98;
        }
    }
}

private double BasePrice => Quantity * ItemPrice;

VS 中操作步骤

  • 选中 Quantity * ItemPrice快速操作和重构提取方法

    csharp
    public double Price
    {
        get
        {
            var basePrice = GetBasePrice();
            if (basePrice > 1000)
            {
                return basePrice * 0.95;
            }
            else
            {
                return basePrice * 0.98;
            }
        }
    }
    
    private double GetBasePrice()
    {
        return Quantity * ItemPrice;
    }
  • 右键 GetBasePrice 方法定义 ⇒ 快速操作和重构使用属性替代:“GetBasePrice”

    csharp
    public double Price
    {
        get
        {
            var basePrice = BasePrice;
            if (basePrice > 1000)
            {
                return basePrice * 0.95;
            }
            else
            {
                return basePrice * 0.98;
            }
        }
    }
    
    private double BasePrice => Quantity * ItemPrice;
  • 右键变量定义 basePrice快速操作和重构内联临时变量

    csharp
    public double Price
    {
        get
        {
            if (BasePrice > 1000)
            {
                return BasePrice * 0.95;
            }
            else
            {
                return BasePrice * 0.98;
            }
        }
    }
    
    private double BasePrice => Quantity * ItemPrice;
  • 还可以优化为使用三元运算符来替换掉 Price 中的 if 语句,然后再应用 使用属性的表达式主题 快速操作。

    csharp
    public double Price => BasePrice > 1000 ? BasePrice * 0.95 : BasePrice * 0.98;
    
    private double BasePrice => Quantity * ItemPrice;

注意

某些类型的临时变量不能是应该改手法,如对照用途的临时变量。

7.5 提炼类(Extract Class)

反向重构内联类(Inline Class)

重构前

csharp
class Person
{
    public string OfficeAreaCode { get; set; }
    public string OfficeNumber { get; set; }
}

重构后

csharp
class Person
{
    private TelephoneNumber _telephoneNumber = new TelephoneNumber();
    public string OfficeAreaCode
    {
        get
        {
            return _telephoneNumber.AreaCode;
        }
        set
        {
            _telephoneNumber.AreaCode = value;
        }
    }
    public string OfficeNumber
    {
        get
        {
            return _telephoneNumber.Number;
        }
        set
        {
            _telephoneNumber.Number = value;
        }
    }
}

class TelephoneNumber
{
    public string AreaCode { get; set; }
    public string Number { get; set; }
}

7.6 内联类(Inline Class)

反向重构提炼类(Extract Class)

和上节 7.5 提炼类(Extract Class) 是互为反向重构的关系。

重构前

csharp
class Person
{
    private TelephoneNumber _telephoneNumber = new TelephoneNumber();
    public string OfficeAreaCode
    {
        get
        {
            return _telephoneNumber.AreaCode;
        }
        set
        {
            _telephoneNumber.AreaCode = value;
        }
    }
    public string OfficeNumber
    {
        get
        {
            return _telephoneNumber.Number;
        }
        set
        {
            _telephoneNumber.Number = value;
        }
    }
}

class TelephoneNumber
{
    public string AreaCode { get; set; }
    public string Number { get; set; }
}

重构后

csharp
class Person
{
    public string OfficeAreaCode { get; set; }
    public string OfficeNumber { get; set; }
}

7.7 隐藏委托关系(Hide Delegate)

反向重构移除中间人(Remove Middle Man)

重构前

csharp
var manager = aPerson.Department.Manager;

重构后

csharp
var manager = aPerson.Manager;

class Person
{
    public string Manager {
        get
        {
            return Department.Manager;
        }
    }
}

7.8 移除中间人(Remove Middle Man)

反向重构隐藏委托关系(Hide Delegate)

和上节 7.7 隐藏委托关系 是互为反向重构的关系。

重构前

csharp
var manager = aPerson.Manager;

class Person
{
    public string Manager {
        get
        {
            return Department.Manager;
        }
    }
}

重构后

csharp
var manager = aPerson.Department.Manager;

7.9 替换算法

重构前

csharp
string FoundPerson(string[] people)
{
    for (int i = 0; i < people.Length; i++)
    {
        if (people[i] == "Don")
        {
            return "Don";
        }
        if (people[i] == "John")
        {
            return "John";
        }
        if (people[i] == "Kent")
        {
            return "Kent";
        }
    }
    return string.Empty;
}

重构后

csharp
static readonly string[] candidates = { "Don", "John", "Kent" };

string FoundPerson(string[] people)
{
    return people.FirstOrDefault(p => candidates.Contains(p)) ?? string.Empty;
}

对于引用类型来说,static readonly 的效果类似于 JavaScript 和 Java 中的 const,不过 C# 中不支持在方法体中定义 static readonly 修饰符的变量。

附 1. 引用

  1. 《重构:改善既有代码的设计》 -- 马丁·福勒(Martin Fowler

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.