Typescript: 允许构造函数可见

创建于 2015-03-13  ·  42评论  ·  资料来源: microsoft/TypeScript

我认为使用静态工厂方法创建类是一个非常常见的模式,而该类的构造函数否则是私有的,因此除非使用factory方法,否则无法实例化该类。

Fixed Suggestion help wanted

最有用的评论

同样适用于类中的私有函数,对吗? 我不明白为什么我们在访问私有构造函数时无法收到编译错误。

所有42条评论

因为所有的“类”实际上只是函数,并且因为没有不可调用的函数,所以在任何可见的类上,您都可以在其上使用new运算符。

但是,您可以使类成为非公共类(例如,在模块中),并导出该类的接口。 这滥用了以下事实:接口可以扩展类,但是它们不能直接实例化或扩展。

同样适用于类中的私有函数,对吗? 我不明白为什么我们在访问私有构造函数时无法收到编译错误。

@billccn我不喜欢通过“ JS允许,那么您将无法隐藏”杀死请求。
事情是完全保护在TS和生成的JS中,另一件事是保护到生成JS的地步。 正如您已经解释的那样,完全保护是不可能的,但是应该由编译器检查不同的可见性。
如果您不喜欢可见性修饰符,那么请在各处使用默认公用,还有其他人认为该概念有用。

是的,我想如果私有字段仅作为编译器检查实现,则可以将其扩展到构造函数。 但是,基于接口的解决方法虽然已经起作用。

:+1:有时候我想强迫程序员使用工厂方法来提高代码的可读性。 基于接口的解决方法在代码中产生了很多噪音。

我认为只有编译器检查才是可行的方法。

接受,接受公关

澄清一下,具有私有构造函数的类是否可以扩展?

即这会引发错误吗?

class A {
    private constructor() {
    }
}

class B extends A { // Should there be an error at A saying: "Cannot extend private class 'A'"?
}

如果是这样,那么我们可以允许这样做:

class A {
    protected constructor(a?: any)
    private constructor() {

    }
}

class B extends A { // No error since 'A' has a non-private constructor
}

从非JS开发人员的经验来看,这是预期的行为。

在第一个示例中, B应该是一个错误,因为其隐式超级调用是非法的。 因此,具有private constructor的类实际上是sealed / final

在第二个示例中, A的声明应该是错误的,因为构造函数的所有重载及其实现都必须具有相同的可见性(对于方法,我们具有相同的规则)。

另请参阅#471。 是否真的需要允许构造函数私有化或将其保护起来?

@benliddicott有时强制单例或迫使程序员使用静态方法之一创建对象有时很有用,因为有时它更具可读性。

这里

@dsherret protected满足了所有这些需求。

但是您不能确定下游用户永远不会有从类继承的合法需要。 private的唯一作用是防止您的下游用户满足您未曾预料到的需求。

@benliddicott有时,您唯一想要的是使类不可扩展。 我推荐您参考有效的Java项目15最小化可变性,尤其是:

“ 2.确保不能扩展该类。这可以防止粗心的或恶意的子类通过改变对象的状态来损害类的不变行为。防止子类化通常是通过将类定为终结来实现的,但是是一种替代方案,我们将在后面讨论。

当前在TypeScript中没有final / sealed支持,因此从类型系统的角度来看,私有构造函数是实现不可变类的唯一方法。 (不过,我确实建议人们也冻结构造函数中的对象。)

@billccn ,该作者的观点很有趣。 图书馆作者的意见应凌驾图书馆用户的意见的想法也是如此。 我自己的经验是,图书馆作者不知道何时使用私有资源,而过度使用私有私有资源,这使用户感到头痛,仅仅是因为他们认为自己知道如何使用私有图书馆,而实际上却不知道。

但是,比起Java这样的静态语言,更合适的比较是Perl,它是另一种动态语言: http :

Perl之所以没有访问修饰符(例如public,private和protected)的原因之一是因为人们认识到它们经常妨碍完成工作:原始设计者的设想与之无关。您想用它做什么。 以同样的方式设计灵活性-尽管您现在可能看不到自己需要灵活性,但是下一个人可能会发现它非常容易解决新问题,并且会祝福您开发这种灵活性的天才;-)

和:

Perl对迷恋的隐私没有痴迷。 宁愿您因为没有受到邀请而留在客厅,而不是因为它有has弹枪

http://www.perlmonks.org/?node_id=1096925

实际上JavaScript是相同的,而且-是的-TypeScript在几乎所有方面都是相同的。 在打字稿中,您可以很好地访问私有成员-使用恰当命名的escape-hatch: obj["propertyName"]

如果作为库编写者您怀疑调用方法或从对象继承是不明智的,则告诉用户这是不明智的。 但是请不要阻止他们-毕竟他们可能比您更了解。

我不理解关于“受保护的足够”的讨论。 如果TS具有可见性的概念,并且我可以将此概念应用于构造函数,那么答案是“还不够”。

如果在构造函数上允许使用访问修饰符,那么我认为它应该与其他修饰符具有一致的行为并允许私有。

一般而言,私人成员肯定有用。 它们使您可以组织和重构类的实现细节,而不必担心在类外部引起副作用。

对于私有构造函数,我可能想强迫团队中的开发人员以某种方式进行编程。 例如,我可能要强迫他们在这里使用static方法,因为它更具可读性,并强迫他们不要扩展此类:

class Currency {
    private constructor(private value: number, private type: CurrencyType) {
    }

    static fromNumberAndType(value: number, type: CurrencyType) {
        return new Currency(value, type);
    }

    static fromString(str: string) {
        const value = ...,
              type  = ...;

        return new Currency(value, type);
    }

    // ... omitted ...
}

// error:
const badCurrency = new Currency(5.66, CurrencyType.USD);
// ok:
const goodCurrency1 = Currency.fromNumberAndType(5.66, CurrencyType.USD);
const goodCurrency2 = Currency.fromString("5.66 USD");

对于私有构造函数,我可能想强迫团队中的开发人员以某种方式进行编程。

那是管理问题,而不是语言设计问题。

@benliddicott对于变量的类型描述符,您可以说类似的内容:)如果您不喜欢该功能,请使用纯JS。 要么
使用TS并创建一些类似于Lint的规则,禁止在构造函数上使用private。换句话说,您的最后一条评论是:“这是工具问题,而不是语言设计问题”。

@benliddicott如果无法执行某些操作,则在进行代码审查后如果出现错误,我将不必将其发回。 这样可以节省时间。

告诉编译器确切的代码使用方法是一种资产,可以为开发人员在编写程序时使用它们提供适当的反馈。

@dsherret不,这是一个任意约束,可赋予Architecture astronauts :-1:

@jbondc这个“建筑宇航员”有什么合理的论点吗? 您试图冒犯或称赞想要此功能的人吗?

@jbondc我不认为“建筑宇航员”一词在这里更多时间在架构上而不是在编写代码上的人吗? 像使用TypeScript这样的语言中几乎使用任何功能一样,决定使用私有构造函数可能是快速而简单的。

另外,我不认为它是“任意的”,因为它可以帮助防止滥用代码。 也许我想提醒自己或我的团队不要使用构造函数,而要使用静态方法之一,或者强制使用静态方法来强制执行单例。 它既快速又简单,我从编译器那里得到了正确的反馈。 如果那是任意的,那么您可能会争辩说语言的许多方面都是任意的。 也许您还有更多话要说,您没有在评论中表达自己的专横性?

这是一种语言功能,如果人们不喜欢它,很容易不使用。

@dsherret还不足以记录文档,而不是强制执行另一个约束吗?

它使多重继承显着复杂化,请参阅#4805(在我看来,这似乎是事后的想法)。 我已经在#3578中表达了我的一些想法,因此不会再去做。 用astronaut表现出一些强势,但这并不意味着要冒犯任何人。

具有私有构造函数的@jbondc类将无法被继承。 它们本质上是密封的,因此继承(更不用说多重继承)不应该与此一起使用。

拥有自我记录代码比在外部或在注释中编写代码要好得多。 这就是我喜欢TypeScript的所有限制的原因之一,并且本质上是我们要求该功能要求的原因-这是使用该语言本身来记录代码的另一种方式。

好了,这是编写示例的另一种方式:

module Currency {
    export enum Type {
        CAD = 1.3,
        USD = 1,
    }

    class PrivateCurrency {
        constructor(private value: number, private type: Type) { }
    }

    export function fromNumberAndType(value: number, type: Type) {
        return new PrivateCurrency(value, type);
    }

    export function fromString(str: string) {
        let value = 10;
        let type = Type.CAD;
        return new PrivateCurrency (value, type);
    }
}

面向对象的数量较少,但实际上您拥有真正的“真正”私人班级。

@jbondc太好了,您找到了参加私人课程的方式! 让其他人使用另一种方法(带有私有构造函数的可导出类)。 现在您可以观察到,有些功能可以满足A组的需求,并且不会破坏B组的工作。 :)

+1

+1

这还在关注吗? 公关真是过时了!

+1

+1

+1

:+1:可用于工厂设计模式

抱歉,这是聚会的重演或只是晚了,但是我们使用以下模式来实现私有构造函数:

interface ExampleBuilder {
    Instance(): Example;
}

export interface Example {
    Val(): number;
}

export let Example: ExampleBuilder = class ExampleImpl {
    constructor(v: number) {
    }

    Val(): number {
        return 42;
    }

    static Instance(): Example {
        return new ExampleImpl(2);
    }
};

let x = Example.Instance(); // OK: x has type Example
let y = new Example(5);     // ERROR: Cannot use 'new' with an expression whose type lacks a call or construct signature.

请注意,可以使用最近合并的readonly修饰符对它进行更多的整理。

@myitcv现在强制执行很酷...我认为这可能是最好的方法。 它仍然太冗长,需要花几秒钟来了解发生了什么,这就是为什么access修饰符在构造函数上仍然非常好。

就个人而言,我只是用// todo: make private once supported注释标记所有将来的私有构造函数,而不调用这些构造函数。 一旦使用此功能,它将很不错,以便通过访问修饰符获得一些实施和更好的文档。

@dsherret

它仍然太冗长,花了几秒钟来了解发生了什么

同意由于这些类是代码生成的,因此我们不必承受太多的认知负担。 因此,与程序员的接口实际上很简单。

嗨,这项功能会在将来的打字稿版本中发布吗?
到目前为止,尝试声明私有或受保护的构造函数会使我在打字稿1.8.10中出现此错误:
错误TS1089:“私有”修饰符不能出现在构造函数声明中。

啊,没关系。 刚刚发现指出该功能的路线图将包含在打字稿2.0中。

啊,没关系。 刚刚发现指出该功能的路线图将包含在打字稿2.0中。

同样,将里程碑设置为TypeScript 2.0并使用标签Fixed表示已将其包括在内。 标有Fixed通常都包含在master中,可以通过npm install typescript@next

我使用TypeScript 2.0.2 RC并在尝试制作private构造函数时仍然得到TS1089。 我做错了吗?还是这根本解决不了?

我正在使用TypeScript 2.0.2 RC,并且在尝试创建私有构造函数时仍会得到TS1089。 我做错了吗?还是这根本解决不了?

它为我工作。 确保您的命令别名指向正确的版本,并且您的编辑器已更新为使用最新的TS版本。

我发现了问题。 这是gulp-typescript的错误,尽管我在package.json了错误的tsc版本,并在PATH上解决了问题。

对于其他任何有此问题的人,我的解决方案是编辑gulpfile.js并...

  1. gulp-typescript之前的require TypeScript如下:
// Tool-Chain: Scripts
var tsc = require("typescript");
var typescript = require('gulp-typescript');
  1. 在定义构建任务时,提供编译器作为替代:
// Task(s): Build TypeScript Outputs
var tsconfig = typescript.createProject("path to tsconfig", { typescript: tsc });
gulp.task('build:scripts', function () {
    let ts = tsconfig.src()
                     .pipe(sourcemaps.init())
                     .pipe(typescript(tsconfig));

    return ts.js.pipe(sourcemaps.write(".")).pipe(gulp.dest(path.join(outputs.root, outputs.scripts)))
});
此页面是否有帮助?
0 / 5 - 0 等级