3

I already have this core class structure that can not be changed:

class A {
    //some basic fields and methods
}
class B {
    //some another basic fields and methods
}

It is core classes and I'm adding some functionality in addition to the existing system. I need to add some common functionality to this classes. It's implementation is almost identical for A and B. My current solution is to build something like this:

class A {
    //some basic fields and methods
}
class B {
    //some another basic fields and methods
}

class Foo extends A {
    //my new features
}
class Bar extends B {
    //my new features (identical with Foo)
}

The problem is that I can't change A or B class and not only I can't change them, I need both base A and B classes and my Foo and Bar classes to exist. So using reflection to change A and B classes is not a solution.

I also tried to add this functionality as some api-object:

class FeatureApi {
    my new features
}
class Foo extends A {
    FeatureApi api;
}
class Bar extends B {
    FeatureApi api;
}

But get another problem: new methods need access to my classes fields/methods. But I don't have them in api-object.

In summary:

  • I can't change existing classes
  • I want to create some custom classes, that extends A and B
  • I don't need access to any private fields.
  • Besides specific changes, my classes will have a lot of identical features for both.

UPD 2 The main purpose is to implement exactly same behaviour in two custom classes, that inherited from two anchangable base classes. As I see now, inheritance of base classes not making any sense, so removed it and clarified my question.

  • 1
    Why not class Foo extends B and class Bar extends Foo ? Or am I missing something? I'm not quite understanding the need for this weird relation between the four classes; I suspect you're using inheritance to solve something that seems composition-related. – Flater Mar 5 '18 at 14:46
  • So, it's because Foo should extend base class A. It is not instance of B. And should not have B behaviour. Only Bar should extend wider class B . In other words, I need to add some features in both A and B classes. But I need to retain old A and B unchanged. This is why I'm creating Foo and Bar. And I can't change existing classes. – TEXHIK Mar 5 '18 at 14:57
  • 1
    Then you're misusing inheritance. Why does Bar need to inherit from Foo? – Flater Mar 5 '18 at 15:03
  • 1
    There are probably some design problems that I don't have enough info to comment on, but from what you did describe - you could probably avoid code duplication by extending from A and B as you did, and extracting the shared code to a third class to which you would pass whatever data it needs. Not great from the OOP point of view, but hey... sometimes you gotta do what you gotta do. – Filip Milovanović Mar 5 '18 at 15:33
  • 2
    Do all of you really not understand what the OP is trying to do? The OP is trying to add common functionality to both A and B. (In fact B extending A is irrelevant.) The OP is (wrongly) trying to do this through inheritance. It would be nice for an answer to explain why inheritance is not appropriate and give some alternate options. – Solomonoff's Secret Mar 5 '18 at 16:16
0

you can do it with your FeatureApi example. you need a couple of modifications to expose the protected fields in A or B to the wrapped FeatureApi

(im going to go with c#)

class FeatureApi 
{
    FeatureApi(IExposeStuff parent) {..}

    public string myNewFeature()
    {
       var info = parent.GetProtectedInfo();
       //do stuff
    }
}

interface IExposeStuff
{
    string GetProtectedInfo();
}

class NewA : A, IExposeStuff
 {
    FeatureApi api;
    public NewA()
    {
        this.api = new FeatureApi(this)
    }

    public string GetProtectedInfo()
    {
        return this.protectedInfo;
    }
}
  • So, I should put reference to my class object into it's behaviour object (modified by extending access to needed fields), right? – TEXHIK Mar 5 '18 at 16:07
  • Well, The code I have supplied is somewhat 'quick and dirty'. there some alternatives, you could inject the needed info as construction parameters or delegates. Which would be neater, but might not work for your specific senario – Ewan Mar 5 '18 at 16:12
  • @TEXHIK: BTW, this is the Adapter pattern - NewA is an adapter that translates the interface you have (here, the one provided by A) to the interface you need (here IExposeStuff). This is the variation of the pattern that uses inheritance. – Filip Milovanović Mar 5 '18 at 16:39
1

Unfortunately you're in a bit of a bind. Someone else locked you into an inheritance hierarchy that you can't change. Depending on how A and B work, you may be forced to extend each class separately to implement your behavior. Here are a few scenarios depending on the constraints you face.

You must override methods from A and B.

In this scenario A and B call their own methods and the functionality you are implementing requires intercepting these calls. For example:

class A {
    private void fizz() {
        buzz();
    }
    void buzz(); // <- you must override this to implement your functionality
}

In this scenario the only way to get your behavior on a raw instance of A is to extend A and override buzz. Similarly, the only way to get your behavior on an instance of B is to extend B and override buzz. You must extend them separately but you might be able to reuse code by making the two overrides of buzz call a static utility method or a separate instance method in A.

This situation is awkward because you're stuck duplicating some code and if you ever have to implement more functionality the same way, now you have 4 classes to extend. If you have to do it yet again you have 8 classes to extend. As you can see this approach doesn't scale well at all.

You need not override methods from A and B.

In this scenario methods from A and B need not "call back" to your new functionality. This scenario might allow you to use delegation via the decorator pattern, depending on how forgiving A and B are. This approach involves extending A with a subclass that takes the "real" instance of A and delegates all method calls to it, implementing your functionality on top of the method calls to A. You would similarly extend B to decorate B.

The one small advantage that this approach has over the first approach is that you should be able to implement any added functionality in terms of utility methods that take the "real" instance of A. As a B is an A, you could call this utility method both from the A decorator and the B decorator.

This approach might not be viable if A only has constructors taking specific data, as any subclass would have to call the superclass's constructors. You could fudge the data passed to the superclass but doing so is hacky and brittle (what if A adds some validation that the fudged data fails?).

You can introduce interfaces.

If you are able to introduce interfaces for A and B then your options open considerably. In this case you could implement the decorator pattern cleanly, without worrying about calling a concrete superclass's constructor. You still need to implement two decorators but, as all functionality you add can be implemented with static utility methods taking the decorated objects, you need only duplicate boilerplate.

Note that with this approach, you need not expose your decorator interfaces. The code would only depend on A and B. Therefore if you need to add more functionality to both A and B, you need only implement it twice. If you need to add functionality a third time, you still need only implement it twice. Therefore this approach scales much better than the first.

  • Thanks for very detailed explanation! It is very cool, but good for me, the answer below deals with my problem well - if I get real object instance in my api method, a can deal with it as if i'm in that object class.And because I'm already creating new classes so I can put anything I need into interface. And I don't need access to private fields/methods. – TEXHIK Mar 5 '18 at 18:01
0

Do not use inheritance or interfaces, they are both inappropriate to your situation. You should use containment instead.

Create a private instance of class A in a new class MyA. Implement your new stuff. Whenever you need some closed functionality of A, call on A. You can create your own new methods that have the same names as the closed methods in A and just delegate. Do the same with B.

  • Problem is not in private access, it is in that functionamity for MyA and MyB is exactly the same. And it will be duplicated in your solution. But this is that a'm trying to avoid. Also, I have clirified my question, read updated version. – TEXHIK Mar 5 '18 at 18:12
  • 1
    @TEXHIK: But, if you don't need access to any of the protected members, then why would this lead to code duplication? Unless you need access to public members of B that are not inherited from A? But you say the functionality is exactly the same, which leads me to believe that you don't? If so, remember that type B is also an A, so you can just write the functionality once, inject a reference to an A (which can be either A or B), and delegate. No duplication in code - you just create two objects. EDIT: Oh, just seen your edit. Is B derived from A or not? – Filip Milovanović Mar 5 '18 at 18:18
  • @FilipMilovanović B extends A in my case, yes. But apart from adding common functionallity into two different classes, the case is in that I need to add it not to A and B, but in Foo and Bar, the Foo and Bar itself should be created too. This is my custom classes, which need to have common functionality, but unfortunately already have parent so I can't use inheritance. So from your comment, we again come to the accepted answer, because you say the same just with adding cast to A)) – TEXHIK Mar 7 '18 at 8:42
0

Why not use encapsulation? Make A and B members of Foo and Bar respectively. Introduce a new layer of abstraction, on whatever is returning the current A & B objects, to do the marshalling, validation etc.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.