2

I currently am working on a project where I have 3 user classes, lets say UserA, UserB, UserC, that inherit from a abstract User class.

The program is supposed to emulate a system wish requires users to login and logout.

The instances of those users are stored in 3 separate lists.

Only one user is "logged" at any time, so I have a User currentUser variable.

When I want to call a method specific to UserA, B or C, I do a cast. How can I avoid doing that cast while keeping the code elegant?

Code ex:

public abstract class User {
}

public class UserA extends User {
    public void clearAgenda();
}

public class UserB extends User {
}

public class UserC extends User {
}

public class System {
    private User loggedUser;

    public void clearUserAgenda() {
        ((UserA) loggedUser).clearAgenda();
    }
}

Note: I know before hand that if a operation is being used, the current user supports that operation. (ex: if System.clearUserAgenda() is called then the user is a instance of UserA)

user2009400
  • 147
  • 9
  • Why three separate lists? – Thorbjørn Ravn Andersen May 22 '14 at 20:26
  • 1
    It sounds a bit dubious that you say that you have the `User currentUser`, but later want to call a method and **know** which type of user this `currentUser` is. How is the `currentUser` stored? And who is **responsible** for calling methods like the `clearUserAgenda` (or other methods that may **only** be called when a particular type of user is the `currentUser`)? That is, how does the caller **know** which type of user is currently logged in? – Marco13 May 22 '14 at 20:27
  • And what is the use cases you are trying to model? – Thorbjørn Ravn Andersen May 22 '14 at 20:27
  • You could have a general method in the `User` interface that `System` calls. Something sufficient vague, like `doWork()` or `processRequest()`. Then the `UserA` implementation would call `clearAgenda()`, and the other implementations would do the correct things for those classes. – Paul Hicks May 22 '14 at 20:38

3 Answers3

4

If you want to avoid the cast, you have to declaire the method clearAgenda() in the Abstract Class User. If you dont do that only UserA can use this method so it must be casted.

Baalthasarr
  • 377
  • 1
  • 13
  • But in a system where those operations are called a lot of times wont doing those casts have a tremendous effect on performance? – user2009400 May 22 '14 at 20:25
  • 1
    Well tremendous is a big word to use here. But yes it will effect performance. However that's why if you introduce it in the abstract class User you can prevent downcasting. You can leave the method 'empty' in the User classes were you don't want to an implementation. – Joel Witteveen May 22 '14 at 20:27
  • 1
    yes because of this look at the answer from mprivat, he illustrated the problem very well. If you put the method in the User class and make it abstract you can override it and implement it in your UserA, etc. Class. If all users do the same you could declaire the method in the User Class and all users (UserA, UserB, etc.) can use this method. – Baalthasarr May 22 '14 at 20:28
3

You pretty much have two options:

  1. Use reflection but that pretty much takes out the "keeping code clean" requirement.
  2. Make all User comply with a common interface (i.e. put clearAgenda() in User, provide a default blank implementation and override it in UserA). Do the same for all the methods.

I recommend option 2. You're running into this conundrum because you're giving the caller of System class the responsibility to know what sort of user you have. System then assumes the caller is right (which is a really bad assumption in the real world) and calls the method blindly. If System can't know, it's ok, but it needs to be able to deal with the user calling the wrong method on the wrong user. For that, make it not be wrong to call clearAgenda() on a UserB. In the superclass method, you can just log that the method was called and not overridden, this way you'll be able to detect problems without crashing the whole thing with a cast exception.

mprivat
  • 21,582
  • 4
  • 54
  • 64
3

It's a clear cut case for polymorphism. You can declare the method in User (as Baalthasarr says), then override it in the subtypes (UserA, UserB, etc) as necessary. Then you just use loggedUser.clearAgenda() and the JRE will figure out which method to use automatically at runtime.

public abstract class User {
    public void clearAgenda() {} // do nothing (or or something if you want)
}

public class UserA extends User {
    @Override public void clearAgenda() { ... } // do stuff specific to A
}

public class UserB extends User {
    @Override public void clearAgenda() { ... } // do stuff specific to B
}

public class UserC extends User {
}

public class System {
    private User loggedUser;

    public void clearUserAgenda() {
        // This will use the most appropriate method. If the type is
        // UserA, UserA's `clearAgenda` method will be used. Similar
        // for UserB. For UserC, The abstract User class's `clearAgenda`
        // method is used, because it was not overridden in UserC.
        loggedUser.clearAgenda();
    }
}

If there's stuff you want to do that's common to all User subclasses, you can put it in User and still run it even if you override the method. For example:

public class UserA extends User {
    @Override public void clearAgenda() {
        super.clearAgenda(); // do the stuff common to all User subclasses
        ... // do stuff specific to A
    }
}
Alvin Thompson
  • 5,388
  • 3
  • 26
  • 39
  • Thank you, just a quick question: should the User.clearAgenda() method trow an exception in case its called (since in theory it should never be called)? ex: if UserC.clearAgenda() is called – user2009400 May 22 '14 at 20:46
  • You could, but it's not necessary (and I wouldn't go out of my way to defer an exception to runtime). If you want to force all subclasses to declare their own version of the method, use this syntax as previously mentioned: `public abstract void clearAgenda();`. – Alvin Thompson May 22 '14 at 20:53