Closed Zorgatone closed 8 years ago
a class with private constructor is not extendable. consider using this instead.
From what I recalled I was sure the compiler didn't like the private keyword on the constructor. Maybe I'm not using the paste version though
This is a new feature, will be released in TS 2.0, but you can try it using typescript@next
. see https://github.com/Microsoft/TypeScript/pull/6885 for more details.
Ok thank you
Doesn't private constructor also make a class not instantiatable out of the class? It's not a right answer to final class.
Java and/or C# uses the final
class to optimize your class at runtime, knowing that it is not going to be specialized. this i would argue is the main value for final support. In TypeScript there is nothing we can offer to make your code run any better than it did without final.
Consider using comments to inform your users of the correct use of the class, and/or not exposing the classes you intend to be final, and expose their interfaces instead.
I do not agree with that, instead I agree with duanyao. Private does not solve that issue, because I also want classes which are final to be instanciateable using a constructor. Also not exposing them to the user would force me to write additional factories for them. For me the main value of final support is, that it prevents users from making mistakes. Arguing like that: What does TypeScript offer to make my code run faster, when I use types in function signatures? Isn't it also only for preventing users from making mistakes? I could write comments describing which types of values a user should pass in as a parameter. It's a pitty, that such extensions like a final keyword are just pushed away, because on my opinion it collides with the original intension of typescript: make JavaScript safer by adding a compilation level, which performs as many checks as possible to avoid as many mistakes upfront as possible. Or did I misunderstand the intention of TypeScript?
There should also be a final
modifier for methods:
class Foo {
final fooIt():void{
}
}
class Bar {
fooIt():void {
}
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.
E.g. I often use following pattern, where I want to urgently avoid fooIt to be overridden:
import Whatever ...
abstract class Foo {
private ImportantVariable:boolean;
protected abstract fooIt_inner:Whatever();
public final fooIt():Whatever() {
//do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
ImportantVariable = true;
//call the abstract inner functionality:
return this.fooIt_inner();
}
}
The argument about cost vs. utility is a fairly subjective one. The main concern is every new feature, construct, or keyword adds complexity to the language and the compiler/tools implementation. What we try to do in the language design meetings is to understand the trade offs, and only add new features when the added value out weights the complexity introduced.
The issue is not locked to allow members of the community to continue adding feedback. With enough feedback and compelling use cases, issues can be reopened.
Actually final is very simple concept, does not add any complexity to the language and it should be added. At least for methods. It adds value, when a lot of people work on a big project, it is valuable not to allow someone to override methods, that shouldn't be overridden.
In TypeScript there is nothing we can offer to make your code run any better than it did without final.
Wow, cringe! Static types don't make your code run any better either, but safety is a nice thing to have.
Final (sealed) is right up there with override as features I'd like to see to make class customizations a bit safer. I don't care about performance.
Static types don't make your code run any better either, but safety is a nice thing to have.
Exactly. Just as private
prvents others from calling the method, final
limits others from overriding the method.
Both are part of the class's OO interface with the outside world.
Completely agree with @pauldraper and @mindarelus. Please implement this, this would make a lot of sense I really miss it currently.
I don't think final is only beneficial for performance, it's also beneficial for design but I don't think it makes sense in TypeScript at all. I think this is better solved by tracking the mutability effects of Object.freeze
and Object.seal
.
@aluanhaddad Can you explain that in more detail? Why do you think it does not "make sense in TypeScript at all"? Freezing or sealing object means to disallow adding new properties to an object, but does not prevent adding properties to a derived object, so even if I would seal the base class I could still override the method in a child class, which extends that base class. Plus I could not add any properties to the base class at runtime.
The idea of using final
on a class or class method in java has more to do with minimizing mutability of the object for thread safety in my opinion. (Item 15. Joshua Bloch, Effective Java)
I don't know if these principals carry over into javascript seeing as everything in JS is mutable (correct me if I'm wrong). But Typescript is not Javascript, yeah?
I would really like to see this implemented. I think it'll help create more robust code. Now... How that translates into JS, it honestly probably doesn't have to. It can just stay on the typescript side of the fence where the rest of our compile-time checking is.
Sure I can live without it, but that's part of what typescript is, right? Double checking our overlooked mistakes?
To me final
would play the same role in typescript as private
or typings, that is code contract. They can be used to ensure your code contract don't get broken. I would like it so much.
@hk0i its also mentioned in Item 17 (2nd edition) in a manner similar to what's been echoed here:
But what about ordinary concrete classes? Traditionally, they are neither final nor designed and documented for subclassing, but this state of affairs is danger- ous. Each time a change is made in such a class, there is a chance that client classes that extend the class will break. This is not just a theoretical problem. It is not uncommon to receive subclassing-related bug reports after modifying the internals of a nonfinal concrete class that was not designed and documented for inheritance.
The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. There are two ways to prohibit subclassing. The easier of the two is to declare the class final. The alternative is to make all the constructors private or package-private and to add public static factories in place of the constructors.
I would argue it does not increase the cognitive complexity of the language given that the abstract keyword already exists. However, I cannot speak to the implementation / performance impact of it and absolutely respect protecting the language from that angle. I think separating those concerns would be fruitful towards deciding whether or not to implement this feature.
I believe that final
would be an excellent addition to seal a class. One use case is that you may have a lot of public methods in your class but expose, through an interface, just a subset of them. You can unit tests the implementation quickly since it has all these public methods while the real consumer uses the interface to limits access to them. Being able to seal the implementation would ensure that no one extends the implementation or change public methods.
You may also ensure that no one is inheriting your class. TypeScript should be there to enforce those rules, and the suggestion about commenting seems to be a lazy approach to solve this use case. The other answer I read is about using private which is only suitable for a particular situation but not the one I explained above.
Like many people in this thread, I would vote to be able to seal class.
@mhegazy Private constructors and sealed/final classes have very different semantics. Sure, I can prevent extension by defining a private constructor, but then I also can't call the constructor from outside the class, which means I then need to define a static function to allow instances to be created.
Personally I'd advocate for having sealed/final compiler checks to ensure that classes and methods marked sealed/final cannot be extended or overridden.
In the context of my problem, I want to be able to have a public constructor so I can instantiate the object, but prevent extension of the class, and only the addition of sealed/final will allow that.
There is a task - write code that
And final
keyword is necessary here, IMHO.
I see final
as a great way to remind yourself and users of your code which protected methods they should be overriding, and which they shouldn't. As a sidenote, I'm also a proponent of explicitly stating that you're overriding, partially so that the reader knows, but also so that the transpiler complains if you typo the method's name.
@zigszigsdk Since methods are never overridden, how would this work?
For final
:
The same way as it works now, except the transpiler would complain if one hides a super's method -which has been declared final- from the this
context.
For override
:
The same way it works now, except the transpiler would complain if one declares a method override
, and its super doesn't have a method by that name, or it does have one but it's declared to be final
.
It could possibly also be warning you if you hide a super's method from the this
context and don't state override.
Java and/or C# uses the final class to optimize your class at runtime, knowing that it is not going to be specialized. this i would argue is the main value for final support.
@mhegazy Not quite! Another important function is to be explicit about which parts of the class expect to be overriden.
For example, see item 17 in "Effective Java": "Design and document for inheritance or else prohibit it":
It is not uncommon to receive subclassing-related bug reports after modifying the internals of a nonfinal concrete class that was not designed and documented for inheritance. The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. There are two ways to prohibit subclassing. The easier of the two is to declare the class final.
Same goes for final methods, in my opinion. It's very rare that a class is designed to support overriding any of its public methods. This would require very intricate design and a huge amount of testing, because you'd have to think of every possible combination of states that might result from combination of non-overrided and overriding behaviour. So instead a programmer might declare all public methods final except one or two, which would decrease the number of such combinations dramatically. And more often that not, one or two overridable methods is precisely what's needed.
I think that final
classes and methods are very important. I hope you will implement it.
Another compelling user case @mhegazy . Today I learned the Template Method Pattern, and found that final
is required to prohibit subclasses changing the template method as the Template Method Pattern in wikipedia says. But I cannot do this in my lovely TypeScript. What a pity!
Template pattern lets subclasses redefine certain steps of an algorithm without changing the algorithm's stucture
For me it's as simple as trying to enforce composition in cases over inheritance. Without final you can't do this.
As much as I'd also like to see final
come to typescript, I think the underlying message that Microsoft is trying to send us is that if we want final
, we should use a language that supports it.
I would like to see this issue re-opend and implemented.
When building a library that you are going to use internally or share publicly (such as on npm), you don't want those who use it to have to browse the code and search through comments or docs to see if the class can/can't be overwritten. If they overwrite it throw an error.
In my code, I have a class that gets extended, and when some sort of event takes place it triggers a method. If the sub-class defines the method it will that one otherwise it will fall back to the default one. However there are also other methods in the class that are not "fallback" methods, they are more of helper methods such as adding an item to the DOM in which case I don't want these methods to get overwritten.
My 5 cents on the topic are that this should be re-opened and implemented.
I would use it to enforce clients of libraries to make their own concrete classes and not extends from some other existing already. Like @lucasyvas said, favor composition. Or just enforce new concrete class implementation to provide to the Dependency Injection in angular for example.
I also vote for re-opening, with support for both classes and methods
I don't say that we shouldn't I am just unsure of how the pattern of the final keyword would work in the context of typescript?
wouldn't the final keyword stop users/ programmer to override/ inherit from the said class/method?
My way of seing Final is that it's "Freeze" the data which mean it's can't be changed anymore. I mean this can be nice to use it's although can be truly an hassle for peoples who want to "bug" fix or change some behavior.
@niokasgami Freeze is related to [data] (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/freeze) and already available as part of ES6. Final applies to classes and methods (variable can be readonly and therefore act as "final" as we cannot override a super variable).
The "Final" keyword is used in JAVA where sealed is in C#.
As typescript is strongly inspired by C#, there should be a higher probability that "sealed" will be the chosen one. Now, we could possibly have confusion with the ES6 seal which basically seals the object at runtime?
@Xample I think you can also use final on local variables in Java. I remember having to do it when I was writing an android app. I think it required me to do it with an event but I am not 100% certain.
function(){
let final myVariable = 'Awesome!'
document.addEventListener('scroll', e => {
console.log(myVariable)
myVariable = 'Not Awesome' // Throws an error
})
}
@TheColorRed Well, final local variable in Java means constant, isn't it?
@EternalPhane I guess so, I have always used const
in a global like scope... Never thought of using it within a function or method... I guess me previous comment is void then...
@Xample Yeah I see your point for Sealed from the C# it's not working as the same Honestly in my opinion BOTH keyword seal and final couldn't really enter the category since both can be confusing (sealed for seal etc) and Final we are unsure how to apply it for since in Java it is used as an constant :/
In java you can final
any variable, method or class. For variables it means the references cannot be reassigned (for non-reference types this means you cannot change their values either). For classes it means no extensions (extends
... keyword). For methods it means no overriding in subclasses.
Main reasons to final
in java:
public static final String KEY = "someStringKeyPathHere"
, useful for reducing mistakes but has an added compile time benefit of not recreating multiple string objects for the same valueI may have left out a couple of use cases but I was trying to keep it very general and give some examples.
@TheColorRed Android requires it when you declare a variable outside of a callback (anonymous class) and then reference it inside. I think this aligns with thread safety issues again. I think they are trying to stop you from reassigning the variable from another thread.
Despite its usefulness I don't think we will see this feature coming any time soon. It might be a good time to look at other solutions or, as in my case, drop typescript all together and deal with the sloppiness in JS. Javascript has a lot of "benefits" by not having any of this type hinting or anything and ultimately your code will be converted to JS anyway. If you want browser compatibility you can write ES6 and use Babel to convert it.
I think realistically even though you can give a slap on the wrist for some of these benefits, you don't get any of the real benefits of the java-esque final
once it's been converted to js.
@hk0i I think I could understand your point but overall I agree and kinda disagree in the same time about your opinion.
Most of the code in typescript are I would say for debugging/tracking purpose for helping the programmer to well I guess do "clean" code (please don't quote me on that I know they have some typescript spaghetti code out here hahah)
most of typescript feature doesn't get transpilled most of the time when converted to JS because they just don't exists in JS.
Although when you runtime/ write your code it's help to see error in your code and to track it more easily than with JS (and HECK JS can be hard to track bugs lol)
So I think the use of the final keyword could be seing as an blocker for API user to know : Oh okay this class shouldn't be extended since it's extension could provoke weird behavior etc...
Typescript as good language it is I find myself sad to find that it lack some important feature such "true" static class keyword who stop peoples from calling the class constructor but still being able to access it.
@hk0i I don't agree neither, the purpose of typescript is to structure the code and therefore providing the tools to do so. We do not HAVE to use final in Java, we typically use it when we know that extending a class or method could lead to introducing side effects (or for any other safety reason).
Relying on the current ES6 solutions adding a runtime mutability check (saying using Object.seal or Object.freeze) makes you loose the benefit of having errors directly when you type your code (despite it could easily be done using this decorator).
@TheColorRed yes… and in typescript we have a distinction for local and class variable.
readonly
for a class variableconst
: for any other variableNone of them would be accurate for method or class. Overriding a method does not mean we are redefining it (think in virtual methods in C++), we just define a method will will be primary be called before the super one (which we can call using super
).
@niokasgami perhaps the naming is not really a problem as we will still understand that Object.seal applied to… an object while sealing a class and a method to the structure itself.
TL;DR:
seal
is widely used in C#, shorter and has the same expected behaviour.By the way I forgot to mention there is also Object.preventExtensions()
Differences can be seen here
reading
updating and deleting
this.aMethod = ()=>{}
. Should methods be readonly
as well to prevent such hacks ? same for delete this.aMethod
creating
class
as the class defined the members of the object, typescript implicitely already prevents from creating new properties on the fly… for instance this.unKnownProperty = "something"
ts will raise a compilation time error TS2339 as expected.extending
final
or seal
would be relevant. The question is how to stay consistant wit the freeze, seal and preventExtensions
of ES6 ? (if we have to).
Extending a class, mean we prevent creating another class from this class. We can then read it but not delete it (who deletes class anyway). The question is not the "update" column. Is a final
class updatable ? What is a class updatable ? My guess is yes… but then the update would be to implement this class (understanding the interface of the class) ? Implementing another class should always be possible… but it's not related to updating the initial class. Well… perhaps they simply came to the same questions writing c# and decided that seal
was the good keyword.overriding
preventOverrides
? or again seal
as it is used in C#BONUS: As sealing a method is usually to prevent people overriding mandatory super code, I made the proposal of forcing people to call the super method (the same behaviour as for the constructor but for any method) don't hesitate to vote up if you think it would be useful.
Don't get me wrong, I'm not against this. I'm very much for it. I'm just trying to get behind Microsoft's paradoxical mindset of why it shouldn't be a feature, which is essentially saying that because it's not a JavaScript feature it cannot be a TypeScript feature.
... If I'm understanding correctly.
@hk0i Generics, readonly, and many other features are not a JavaScript features, but they are still added into TypeScript, so I don't think that is the reason...
TypeScript only shies away from features which require generating code in a non-straightforward way. Generics, readonly, etc are purely compile-time notions which can be simply erased to produce the generated output, so they’re fair game.
final
can be "erased" at compile time too, so why doesn't that make it "fair game"?
🤷♂️
@TheColorRed I think this more or less this either they have other priority to integrate. Or they unsure how predictable this new keyword could work on compile/ coding time. if you think about it the design of final keyword can be extremelly vague. final could be used a lots for many purpose. As you can see from the Jsdoc documentations you can use the tag "@final" which tell the jsdoc interpreter that this variable is frozen thus readonly.
here a design clash. Readonly is aplicable on variable and getter if I recall which "froze" the variable and make it readonly and final make it final thus readonly as well.
this can cause confusion to the programmer who is unsure if they should tag their variables final or readonly.
UNLESS we reserve this keyword exclusively for class and method declarations, I think the behavior or final would clash with readonly.
I think instead of sealed (which can clash with object.seal) or final (which this purpose design clash readonly keyword) we could go with a more "direct" way to name and design it.
take note this merelly a "idea" of a behavior who is similar but in same time different from the C# behavior? I took some inspirations from how C# is normally nonOverridable and then you have to say that this method is virtual.
` namespace Example {
export class myClass {
constructor(){}
// Make the func non ovveridable by inheritance.
public unoveridable myMethod(){
}
public myMethodB(){
}
}
export class MyClassB extends myClass {
// Nope not working will throw an error of an nonOverridable error
myMethod(){
super.myMethod();
}
// Nope will not work since unoverridable.
myMethod(){
}
// Design could be implemented differently since this could complicate ¸
// the pattern of typescript
public forceoverride myMethod(){
// destroy previous pattern and ignore the parent method thus a true ovveride
}
// Yup will work since it's still "virtual".
myMethodB(){
super.myMethodB();
}
}
// Can extend an existing Parent class
// Declaring an unextendable class make all is component / func nonOverridable by
// inheritance. (A class component can still be overwritten by prototype usage.)
export unextendable class MyFinalClass extends Parent {
constructor()
// nonoverridable by default.
public MyMethod(){
}
}
// nope throw error that MyFinalClass is locked and cannot be extended
export class MyChildClass extends MyFinalClass{}
}`
Edit : I didn't include Variable and get since readonly already exists.
@mhegazy Please consider reopening this issue. The community response seems to be overwhelmingly on the side of adding a final
keyword, like Java (final
), C# (sealed
, readonly
), PHP (final
), Scala (final
, sealed
), and C++ (final
, virtual
) have. I can't think of a statically typed OOP language that doesn't have this feature, and I haven't heard a convincing argument for why TS doesn't need it.
I was thinking it could be useful to have a way to specify that a class should not be subclassed, so that the compiler would warn the user on compilation if it sees another class extending the original one.
On Java a class marked with final cannot be extended, so with the same keyword on TypeScript it would look like this: