-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: Permit an implementing class to ignore private methods of the implementee class #471
Comments
Is there a reason MockFoo must implement Foo and not extend it? |
@danquirk, yes, it would be ideal for However, that doesn't satisfy the second requirement:
To elaborate, suppose we now extend class MockFoo extends Foo {
public writeToFile(){
// do nothing
}
} Somewhere else in code, a function that we wish to test: function bar(foo: Foo){
foo.writeToFile();
} Here's a test case for function function test(){
bar(new MockFoo()); // All good
} Following on from this, if in the next iteration we carry out the following refactor: class Foo {
// Renamed from "writeToFile"
public writeToFileSync(){
fileWriter.writeToFile('');
}
} The compiler does not issue an error for Viewed from a different perspective, the compiler isn't able to issue an error, because the deriving class has no mechanism to indicate that it is overriding a base class method. If such a mechanism were to exist then that would also solve this problem. |
A possible solution that occurred to me was to define class MockFoo extends Foo {
public writeToFile(){
super.writeToFile;
// do nothing
}
} Apart from the slightly hackish nature of the solution, there was a problem with this, which I cannot recall... |
You could also create an interface interface IFoo {
writeToFile(): void;
}
class Foo implements IFoo {
// ...
}
class MockFoo implements IFoo {
// ...
}
function bar(foo: IFoo) {
foo.writeToFile();
} |
The problem is that if you have a private field, class Foo {
private p = 'hello';
static doSomething(n: Foo) {
console.log(n.p);
}
}
var x = new MockFoo();
Foo.doSomething(x); // Fails We don't want to water down the meaning of |
That's a fair point. On the other hand, I cannot see any use-cases for the idea of "one class implementing another class", because it's limited to only those classes that don't have any private fields or methods - which are not really common in the real world. Perhaps we could invoke this design goal:
To me the idea of being able to use the public API of a class as an interfaces is quite appealing; and probably has many interesting use-cases in the real world. |
This isn't true. The useful example for this is: class Animal {
private something;
}
class Dog extends Animal {
woof() {}
}
// Error, Wolf doesn't have 'woof' method
class Wolf extends Animal implements Dog {
} |
I'm not entirely convinced that that example has significantly increased the usefulness of "implementing a class", because now |
The other problem that I mentioned with extending the real class was to do with the fact that if the real class had complex constructor parameters then that would be inherited by the mock class. Ideally one would want to simply write |
Tagging Suggestion, but understand we're unlikely to make a breaking change here. Other languages use interfaces for this scenario and I think that's still an appropriate solution for TypeScript. We'd need a compelling use case here along with a non-breaking design proposal. |
Why not simply allow implementing private members of class ? class Foo {
private x = 1;
}
class MockFoo implements Foo {
private x = 1;
} This is less nice than being able to ignore private members but at least you can do mocks and it doesn't break anything. |
Yes, this would be an improvement on the current situation, because as it stands the idea of "implementing another class" is not useful. |
Note #1101 asks the same question of |
I am still trying to understand what the language of Can someone reiterate why you can't just "ignore private variables when implementing interfaces"? I agree with @NoelAbrahams that I look at interfaces as a public API definition.
|
Even though it isn't distinguishable whether a type has privates from outside the class, an instance of See @RyanCavanaugh's example in the above #471 (comment). |
@DanielRosenwasser Okay thank you. That clarifies it for me. I saw that example but was confused because I was under the impression that |
I've opened a related issue #3854 which would solve the originators motivating problem. |
see alsp #4278 |
This is NeedsProposal. Here's a proposal which would allow the workaround posted to #3854 and #4278 work for Proposal: Allow an extending class to modify the protection of a private member.Currently an extending class can modify a class X{
protected m1: string;
}
class Y extends X {
public m1: string;
} The proposal is to allow the same syntax to expose a class P{
private m1: string;
}
class Q extends P {
protected m1: string; // Currently an error
} |
These are reasonable use cases, but the problem is that we don't want If we get a general notion of type operators (many issues wanting this), a simple operator e.g. |
I'm trying to understand the motivations regarding the language design here. Again I wish to reduce brain pain, hope you don't mind. I do understand why privates currently need to be exposed, as the scope of the privates are bound to the class and not the instance. I don't understand, considering the consequences, why the scope of privates are bound to the class and not to the instance. Is there any significant value in that a Foo can manipulate the implementation details of any Foo? Something that makes up for the non-intuitiveness and the disturbed ability of the language to easily provide abstractional reusability that comes from mixing in privates with types/interfaces? I would like to be able to explain to my colleagues what makes this strange deal with exposed privates worth it after all. |
Although that would further complicate this whole concept of privates and what it actually means. Same thing with letting subclasses upgrade private and protected fields. Wasn't even aware of that subclasses already can update protected fields, feels like a complete violation of why we have those modifiers in the first place. Feels like this can get messy. |
Certainly TypeScript is not the first language deciding to scope |
Thank you for your response Ryan. Sure, but TypeScript differs from those languages as it's structurally typed. And that means that we suddenly have this problem with privates having to be propagated in the type system, a problem that these other languages do not experience. I personally believe that solid abstractural integrity is significantly more important than allowing a Foo to mess around with the internals of other Foos. But if the reason for this is that the other languages do it, then I at least know what to say to my colleagues. Brain still not happy, but less pain. Thanks. |
I don't think a class having access to/visibility of other instances breaks encapsulation at all. The proposal for ES private properties also includes this class level visibility. It is simply a different form of logical state which the class has access to. I like to think of this private state as a class visible WeakMap, and in fact for @dojo this is actually what we do with our private state, versus using The main use case is that the class having access to the private properties allows for static methods to potentially operate on instances without needing to break this encapsulation. |
@kitsonk * And then we have this stuff about promoting protected members, which I'm really curious about why we got in the first place, and the suggestion about doing the same thing with private members. Talk about breaking encapsulation. For a language whose selling point is to be able to scale, it doesn't feel good at all. |
I like the idea of
|
If this problem is going to remain, you should at least prevent class from "implementing" other classes, imo. |
Since mapped object types strip out type Interface<T> = {
[P in keyof T]: T[P]
}
class C {
private foo(): string;
bar(): number;
}
// Look ma, no 'foo'!
class D implements Interface<C> {
bar() {
// ...
}
} I've also called that mapped object type |
Hi, we have a requirement to create mocks of real classes defined in code. The exact requirements are
A possible solution might be as follows:
This appears to solve the problem because changing
Foo.writeToFile
will trigger a compilation error along the lines of "Class MockFoo declares interface Foo but does not implement it..."The problem with this approach is that
class Foo
is not permitted to have any private methods or fields. If we were to add a private methodfoo()
toclass Foo
then it's no longer possible forMockFoo
to implementFoo
because the compiler doesn't permit it.I suggest that when a class implements another class the implementing class be allowed to ignore the private fields and methods (both static and instance) of the implementee.
(I am aware that there are workarounds, such as declaring an interface that both
Foo
andMockFoo
implement, but that introduces an unnecessary maintenance overhead.)The text was updated successfully, but these errors were encountered: