只有你能 new 出来!.NET 隐藏构造函数的 n 种方法(Builder Pattern / 构造器模式)

203 篇文章 2 订阅

如果你给类写了一个公有构造函数,那么这个类就能被其他开发者 new 出来。如果你不想让他们 new 出来,把构造函数 private 就好了呀。

然而还有更多奇怪的方式来隐藏你类的构造方法。


为什么要隐藏构造函数?

有些类型,只有组件的设计者才知道如何正确创建其类型的实例,多数开发者都无法正确将其创建出来。典型的如 string:绝大多数开发者都不能正确创建出 string 的实例,但通过写一个字符串由编译器去创建,或者使用 StringBuilder 来构造则不容易出错。

再或者,我们只希望开发者使用到某个抽象的实例,而不是具体的类型,那么这个时候开发者也需要有方法能够拿到抽象接口的实例。我们可能会使用工厂或者某些其他的方法让开发者在不知道具体类型的时候获取到抽象类型的实例。

这正是构造器模式的典型应用场景。在维基百科中对它适用性的描述为:

在以下情况使用生成器模式:

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时;
  • 当构造过程必须允许被构造的对象有不同的表示时。

详见:生成器模式 - 维基百科,自由的百科全书

接下来,我们使用一些奇怪的方式来创建对象的实例,完完全全把构造函数隐藏起来。

隐式转换和显式转换

典型的像 long a = 1;bool? b = true 这都是语法级别的隐式转换。这真的只是语法级别的隐式转换,实际上这两个都是编译器原生支持,编译时即已转换为真实的类型了。

[System.Runtime.Versioning.NonVersionable]
public static implicit operator Nullable<T>(T value)
{
    return new Nullable<T>(value);
}

[System.Runtime.Versioning.NonVersionable]
public static explicit operator T(Nullable<T> value)
{
    return value.Value;
}

于是我们可以考虑写一个神奇的类,其创建是通过隐式转换来实现的:

Fantastic fantastic = "walterlv";
Console.WriteLine(fantastic);

以上代码的输出是 walterlv is fantastic

namespace Walterlv.Demo.Patterns
{
    public class Fantastic
    {
        private readonly string _value;
        private Fantastic(string value) => _value = value;
        public static implicit operator Fantastic(string value) => new Fantastic(value);
        public override string ToString() => $"{_value ?? "null"} is fantastic.";
    }
}

而使用显式转换,我们还可以写出更奇怪的代码来。比如下面这个,我们的实例是通过强制转换一个 null 来实现的:

Fantastic fantastic = (IFantastic) null;
Console.WriteLine(fantastic);

以上代码的输出是 is fantastic 字符串。呃……前面有个空格。

namespace Walterlv.Demo.Patterns
{
    public class Fantastic
    {
        private readonly IFantastic _value;
        private Fantastic(IFantastic value) => _value = value;
        public static implicit operator Fantastic(IFantastic value) => new Fantastic(value);
        public override string ToString() => $"{_value} is fantastic.";
    }

    public class IFantastic
    {
    }
}

那个 IFantastic 必须得是一个类,而不能是接口,因为隐式转换不能从接口转,也不能转到接口。

在这里插入图片描述
▲ 不能定义从接口进行的隐式转换

运算符重载

使用运算符重载,也可以让类型实例的构造隐藏起来。比如下面的 Scope 类型,从字符串创建,然后通过与不同的字符串进行位或运算来得到其他的 Scope 的实例。

Scope scope = "A";
var full = scope | "B" | "C";
Console.WriteLine(full);

当然这段代码也少不了隐式转换的作用。

以上 Scope 类型的实现在 github 上开源,其表示 OAuth 2.0 中的 Scope

ERMail/Scope.cs

关于运算符重载的更多内容,可以参考我的另外两篇文章:


我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值