1

I saw that in Typescript you can emulate module visibility with interfaces, but I don't know if it is possible to achieve it in the following scenario:

abstract class ConnectionTarget
{
    // callback that subclasses must implement
    protected abstract onConnection: (conn: Connection) => void;

    // property that must be available to subclasses
    protected get connections(): Readonly<Iterable<Connection>>
    {
        return this.conns;
    }

    // private field needed for previous property
    private conns: Connection[] = [];

    // method that SHOULD HAVE MODULE VISIBILITY
    // my module should be able to add connections,
    // but my users shouldn't
    private addConnection(conn: Connection)
    {
        this.conns.push(conn);
        this.onConnection(conn);
    }
}

// my function that needs access to the private members
// the parameter is a user-provided subclass of ConnectionTarget
function doMagicThings(target: ConnectionTarget, source: ConnectionSource)
{
    // do magic tricks here ...

    // method that should be module-protected, like addConnection
    let aConnection: source.createConnection();

    target.addConnection(aConnection);
}

I'd like my users to extend ConnectionTarget, having to implement onConnection and being able to only use the property connections, with everything else hidden to them.

EDIT: example usage

// class in user code
class MyConnectionTarget extends ConnectionTarget
{
    // users must implement this abstract method
    onConnection(conn: Connection)
    {
        // user specific code here
        // ...

        // can use property 'connections'
        console.log(this.connections)

        // should error here:
        // should not allow to use the following method
        this.addConnection(new Connection());
    }
}
10
  • That depends on your definition of "hidden". Do you want it to be hidden at compile time or run time? Commented Feb 21, 2017 at 9:11
  • I don't know if I understand you correctly, but I'd say at compile time. I mean, trying to use those methods should give a compiler error, as it does in any typed language with visibility modifiers, like "method xyz is private and cannot be used". Commented Feb 21, 2017 at 9:13
  • Or "method xyz does not exist", which is the same thing from the user perspective. Commented Feb 21, 2017 at 9:13
  • Ok, and what's your current problem then? A subclass of ConnectionTarget shouldn't be able to access private methods/members. Commented Feb 21, 2017 at 9:17
  • I need addConnection visible to other things in my module. Otherwise, I have no way to add connections to the class. Commented Feb 21, 2017 at 9:18

1 Answer 1

1

You can do that by exporting an interface which declares the public methods without exporting the class itself.
You will then need a factory function which is exported by the module to be able to instantiate the class, something like:

export interface IConnectionTarget {
    // public methods will be declared here, i.e:
    myMethod(): void;
}

abstract class ConnectionTarget implements IConnectionTarget {
    private conns: Connection[] = [];

    protected abstract onConnection: (conn: Connection) => void;

    protected get connections(): Readonly<Iterable<Connection>> {
        return this.conns;
    }

    public addConnection(conn: Connection) {
        this.conns.push(conn);
        this.onConnection(conn);
    }

    public myMethod() {}
}

export function createConnectionTarget(): IConnectionTarget {
    // create an instance here and return it
}

(code in playground)


Edit

Without understanding what you're trying to do better, at seems that you have a few options, but none of them is very pretty:

(1) Keep the method private and when trying to access it cast to any:

let aConnection: source.createConnection();
(target as any).addConnection(aConnection);

(2) Save the setter in the ctor to a module level store:

type Adder = (conn: Connection) => void;
const CONNECTION_ADDERS = new Map<ConnectionTarget, Adder>();

abstract class ConnectionTarget {
    protected constructor() {
        CONNECTION_ADDERS.set(this, this.addConnection.bind(this));
    }

    private addConnection(conn: Connection) { ... }
}

And then to use it:

let aConnection: source.createConnection();
CONNECTION_ADDERS.get(aConnection)(aConnection);
Sign up to request clarification or add additional context in comments.

3 Comments

I thought of this, but I don't think it works. If my user only sees IConnectionTarget, how can he implement the abstract method?
The plot thickens. Can you please add to your question a code describing how you will use this addConnection method from within the module? That is, there might be several instance of this class (or sub classes), how does the module add connection to a specific instance?
Thank you very much. I didn't thought about these methods. In particular, casting to any is simple and works quite well, given that the "hack" is limited to internal code, and not visible to the users.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.