Page 3 of 3 FirstFirst 123
Results 21 to 26 of 26
  1. #21
    Join Date
    Dec 2002
    Posts
    39
    1st: Flip the tree. Omnivore is the root. It has hunt and graze. Carnivore and herbivore descend from omnivore. Hunt and graze are overridden appropriately.

    2nd: Decorator pattern. Without default behaviors: ICarnivore, IHerbivore. Implement based on what the creature does. An omnivore becomes an omnivore by definition, by implementing both interfaces.

    3rd: Encapsulation. Implement base Creature class. Has IsCarnivore, IsHerbivore indicators. Implement Eat(Food food). In Eat check the type of food against the IsCarnivore/IsHerbivore indicators. Encapsulate (or derive) from the Creature class into Carnivore or Herbivore; in the new classes implement Hunt and Graze which are facades for Eat.

    The above methods are "more work" but provide a cleaner implementation in my opinion. For instance, in Graze, rather than automatically calling the base Eat you could do a preliminary check. Perhaps the creature has an aversion to shamrock; we could filter that and other Food prior to calling Eat.

    There's no single way to do these things; it boils down to personal experience and desire. I'm not saying my way is the best way -- obviously we both feel strongly on the topic and we'll probably not come to an agreement, other than to agree to disagree.

  2. #22
    Join Date
    Feb 2005
    Location
    Bellevue, WA
    Posts
    3,251
    One of my points is that with multiple inheritance you don't need to contrive a "solution" to this problem.

    The first one is just wrong though.

    Code:
    Ominvore o;
    Carnivore c;
     
    o.graze(); // This is valid
    c.graze(); // This should not be as carnivore's only "eat"
    With your second solution, you've essentially already done multiple inheritance; in C# you just aren't allowed to define a base implementation. But a tree diagram of the two look identical in the C++ view and the C# view; this is a case where C# requires more code though.

    The third strategy also poses a scaling problem and doesn't allow you to use the benefits (and dangers) of polymorphism. Creature runs a very high risk of becoming a "god" class for all creatures with many different special cases depending on the type of creature. For instance, if we add creatures that can swim, fly, and walk on land are those properties added to the Creature class?

    You're right, there is no "one" way do to things. However, there are designs that lead to cleaner and more maintainable code. Sure, multiple inheritance can be abused, but so can delegates, virtuals, manual GC collections, etc...

    I wouldn't say I feel that strongly on the topic, my overall point is that in this case C++ has a cleaner solution to the problem over C# and the exclusion for this in C# is frankly ridiculous given the amount of complexity that has actually been added to the language (remember, the argument against multi-inheritance was complexity). i.e. metadata, lambda expressions, anonymous functions, unsafe code regions, p/invokes, reflection...

    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." ~Rich Cook

  3. #23
    Join Date
    Dec 2002
    Posts
    39
    Quote Originally Posted by owensd View Post
    One of my points is that with multiple inheritance you don't need to contrive a "solution" to this problem.

    The first one is just wrong though.

    Code:
    Ominvore o;
    Carnivore c;
     
    o.graze(); // This is valid
    c.graze(); // This should not be as carnivore's only "eat"
    I believe the first problem you bring up is represented in quite a few multiple inheritance scenarios. Without very careful planning and design your children classes will end up with capabilities that make no sense in their definition -- they are there because they're baggage brought along with the functionality you did want.

    Quote Originally Posted by owensd View Post
    With your second solution, you've essentially already done multiple inheritance; in C# you just aren't allowed to define a base implementation. But a tree diagram of the two look identical in the C++ view and the C# view; this is a case where C# requires more code though.
    True, the second problem is multiple inheritance -- but limited. An interface is created to support the truly common functionality and attributes between two classes; you must make a conscious decision to create the interface, and then you must implement it. Yes, there is duplicate code and effort required -- but it's an explicit binding.

    Quote Originally Posted by owensd View Post
    The third strategy also poses a scaling problem and doesn't allow you to use the benefits (and dangers) of polymorphism. Creature runs a very high risk of becoming a "god" class for all creatures with many different special cases depending on the type of creature. For instance, if we add creatures that can swim, fly, and walk on land are those properties added to the Creature class?
    This isn't an immediate problem; encapsulation does fail once the amount of common functionality becomes anything more than limited. At the point of adding everything else in the above list you'd need a refactor -- and you might very well curse the lack of multiple inheritance.

    Quote Originally Posted by owensd View Post
    I wouldn't say I feel that strongly on the topic, my overall point is that in this case C++ has a cleaner solution to the problem over C# and the exclusion for this in C# is frankly ridiculous given the amount of complexity that has actually been added to the language (remember, the argument against multi-inheritance was complexity). i.e. metadata, lambda expressions, anonymous functions, unsafe code regions, p/invokes, reflection...
    I believe (read: this is my belief, not what is commonly stated) the decision originated from the need to have a common Object ancestor, and the ability to reliably track allocated memory and the types associated -- and the simplicity that a single ancestor provided. This doesn't even begin to touch on the complexities introduced by multiple inheritance and the effort required by language compilers and/or run time to support it. Still... the complexity argument never sat well with me; give power to the programmer and allow them to wield as necessary.

    ----

    You've touched on the issues with implementing without multiple inheritance. Let's consider the issues with multiple inheritance.

    The garbage collector issue can be quite troublesome. Consider A -> B, A -> C, and D -> B:C. How many instances of A does the object hold? How about recursive dependencies within the object? Garbage collectors have enough work to do as fast as possible without having to take into account the possibility of multiple inheritance trees which is providing no benefit 99.9999% of the time.

    Then you have the classic B.foo() and C.foo() issue. When foo() is called in D, how do you know which version of foo is called? What about when the reference is abstract or interface based, and you can't specify the parent of the method to be executed?

  4. #24
    Join Date
    Feb 2005
    Location
    Bellevue, WA
    Posts
    3,251
    Quote Originally Posted by Trystan View Post
    You've touched on the issues with implementing without multiple inheritance. Let's consider the issues with multiple inheritance.

    The garbage collector issue can be quite troublesome. Consider A -> B, A -> C, and D -> B:C. How many instances of A does the object hold? How about recursive dependencies within the object? Garbage collectors have enough work to do as fast as possible without having to take into account the possibility of multiple inheritance trees which is providing no benefit 99.9999% of the time.
    Why would there ever be multiple data regions for A if a class or classes inherited some descendants of A. A -> B -> C -> D, how many copies of A are there? Each class in the chain is of type A, so does D have multiple copies of A? No... so I guess I don't see how D -> B:C is different in this regard. Maybe I'm missing something. The solution in C++ is the same with virtual inheritance, my recommendation is that virtual inheritance is just the default as that's the only way it makes sense to do it. Data regions should not be duplicated and ambigous member functions are forced to be fixed by the user at compile time.

    My opinion is that C++ did it wrong by forcing the virtual on the middle classes of the diamond (B and C) and should have allowed D (or the compiler) to resolve the data region issues for you.

    Yes, garbage collection could be tricky. But all of that information is known at compile time and the class can be marked as 'multi-inherited' and wouldn't need to take into account multiple inheritance tress anytime it wasn't necessary.

    Quote Originally Posted by Trystan View Post
    Then you have the classic B.foo() and C.foo() issue. When foo() is called in D, how do you know which version of foo is called? What about when the reference is abstract or interface based, and you can't specify the parent of the method to be executed?
    How is this issue any different from this?

    Code:
    public interface IFoo {
        void Bar();
    }
    public interface IBar {
        void Bar();
    }
    public class UhOh1 : IFoo, IBar {
        void IFoo.Bar() { Console.WriteLine("IFoo!"); }
        public void Bar() { Console.WriteLine("IBar!"); }
    }
    public class UhOh2 : IFoo, IBar {
        public void Bar() { Console.WriteLine("IFoo!"); }
        void IBar.Bar() { Console.WriteLine("IBar!"); }
    }
     
    public class UhOh3 : IFoo, IBar {
        public void Bar() { Console.WriteLine("IFoo!"); }
    }
     
    class Program {
        static void Main(string[] args) {
            UhOh1 o1 = new UhOh1();
            o1.Bar();
            UhOh2 o2 = new UhOh2();
            o2.Bar();
            UhOh3 o3 = new UhOh3();
            o3.Bar();
        }
    }
    It's not ambiguous nor is it ambiguous in C++. You must fix the compile error that results in C++ to get rid of the ambiguity, here in C# if we define both implementations of Bar() we need to specify which interface it belongs to. If we only define one, it doesn't matter for interfaces (or pure/abstract virtual methods) as the signatures match.

    There is a semantic problem here with UhOh3 (though contrived), which interface implementation does Bar() really belong to? Sure, the signatures are the same, but are they equivalent? That is, is IBar and IFoo interchangeable?

    Edit:
    I agree completely that it is harder from a compiler implementation standpoint to handle multiple inheritance.
    Last edited by owensd; 05-11-2010 at 03:14 PM.

    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." ~Rich Cook

  5. #25
    Join Date
    Dec 2002
    Posts
    39
    Quote Originally Posted by owensd View Post
    Why would there ever be multiple data regions for A if a class or classes inherited some descendants of A. A -> B -> C -> D, how many copies of A are there? Each class in the chain is of type A, so does D have multiple copies of A? No... so I guess I don't see how D -> B:C is different in this regard. Maybe I'm missing something. The solution in C++ is the same with virtual inheritance, my recommendation is that virtual inheritance is just the default as that's the only way it makes sense to do it. Data regions should not be duplicated and ambigous member functions are forced to be fixed by the user at compile time.
    B and C are compiled separately; A exists inside both of these classes. Now you're inheriting directly from B and C, whose definitions (and initialization code) are already set. How far does the compiler go in order to resolve the issue? What extent is it allowed to recognize and rewrite in order to correct this situation? Is the size of D less than the size of B and C combined because the compiler combines the data regions? What if both B and C virtualize the functionality of a method inside A, yet we reference it using the type of A from code which is attempting to deal with objects in a non specific manner (and may have no knowledge of B, C, or D). Which functionality should be executed? What if B and C both depend upon A behaving a certain way, of certain parameters being within certain limits, and the aggregation of the parent class erodes this? Which concrete implementation of A is used to resolve these?

    Quote Originally Posted by owensd View Post
    It's not ambiguous nor is it ambiguous in C++. You must fix the compile error that results in C++ to get rid of the ambiguity, here in C# if we define both implementations of Bar() we need to specify which interface it belongs to. If we only define one, it doesn't matter for interfaces (or pure/abstract virtual methods) as the signatures match.

    There is a semantic problem here with UhOh3 (though contrived), which interface implementation does Bar() really belong to? Sure, the signatures are the same, but are they equivalent? That is, is IBar and IFoo interchangeable?

    Edit:
    I agree completely that it is harder from a compiler implementation standpoint to handle multiple inheritance.
    Your example doesn't quite fit, and unfortunately I can't produce code which would be legal and demonstrate the issue at hand.

    Code:
    class Creature
    {
         public virtual string ToString() { Console.WriteLine("Creature."); }
    }
    
    class LeftHanded : Creature
    {
         public override string ToString() { Console.WriteLine("Left Handed Creature."); }
    }
    
    class RightHanded : Creature
    {
         public override string ToString() { Console.WriteLine("Right Handed Creature."); }
    }
    
    class Ambidextrous : LeftHanded, RightHanded
    {
    }
    Code:
         Creature creature = new RightHanded();
         Console.WriteLine(creature.ToString()); // "Right Handed Creature."
        
         creature = new LeftHanded();
         Console.WriteLine(creature.ToString()); // "Left Handed Creature."
    
         creature = new Ambidextrous();
         Console.WriteLine(creature.ToString()); // ??
    Consider a consumer of your class. They have no idea what the structure of the class is, or what other classes it's derived from -- but when they attempt the code above they get some nasty C++ mangled complaint about specifying the implementation to be used.

    How is an interface implementation different? It's not, really. You end up dealing with the same crap, specifying the implementation to be used -- but again, the actions are more explicit, the implementations have to be created for each interface -- you're not allowed to assume code will be there.

    Multiple inheritance has been around forever and a day - obviously all of the problems associated with it can be overcome or disposed of. I'll be honest - most of the folks I work with on a day to day basis can't add a web service reference to their project -- I'm not going to trust them in any way, shape, or form with multiple inheritance.

  6. #26
    Join Date
    Feb 2005
    Location
    Bellevue, WA
    Posts
    3,251
    Quote Originally Posted by Trystan View Post
    B and C are compiled separately; A exists inside both of these classes. Now you're inheriting directly from B and C, whose definitions (and initialization code) are already set. How far does the compiler go in order to resolve the issue?
    The entire chain, no different than it does now.

    Quote Originally Posted by Trystan View Post
    What extent is it allowed to recognize and rewrite in order to correct this situation? Is the size of D less than the size of B and C combined because the compiler combines the data regions?
    It doesn't have to rewrite anything. Yes, the size of D is less than the sum of the size of B and C the same way the sum of the sizes of fields is different than the size of a type when using unions.

    Quote Originally Posted by Trystan View Post
    What if both B and C virtualize the functionality of a method inside A, yet we reference it using the type of A from code which is attempting to deal with objects in a non specific manner (and may have no knowledge of B, C, or D). Which functionality should be executed?
    Ambiguous compiler error. How does the compiler/linker have no knowledge of B, C, or D? Plugins get loaded into the runtime and call lookups happen for this already, possibly resulting in a runtime failure for an ambiguous method call.

    Quote Originally Posted by Trystan View Post
    What if B and C both depend upon A behaving a certain way, of certain parameters being within certain limits, and the aggregation of the parent class erodes this? Which concrete implementation of A is used to resolve these?
    This a generic inheritance issue not specific to multiple inheritance. There is only one concrete implementation of A, everything else would be virtual.


    Quote Originally Posted by Trystan View Post
    Your example doesn't quite fit, and unfortunately I can't produce code which would be legal and demonstrate the issue at hand.

    Code:
    class Creature
    {
         public virtual string ToString() { Console.WriteLine("Creature."); }
    }
    
    class LeftHanded : Creature
    {
         public override string ToString() { Console.WriteLine("Left Handed Creature."); }
    }
    
    class RightHanded : Creature
    {
         public override string ToString() { Console.WriteLine("Right Handed Creature."); }
    }
    
    class Ambidextrous : LeftHanded, RightHanded
    {
    }
    Code:
         Creature creature = new RightHanded();
         Console.WriteLine(creature.ToString()); // "Right Handed Creature."
        
         creature = new LeftHanded();
         Console.WriteLine(creature.ToString()); // "Left Handed Creature."
    
         creature = new Ambidextrous();
         Console.WriteLine(creature.ToString()); // ??
    This is the heart of my example. This is a compiler error as the call is ambiguous.

    Quote Originally Posted by Trystan View Post
    Consider a consumer of your class. They have no idea what the structure of the class is, or what other classes it's derived from -- but when they attempt the code above they get some nasty C++ mangled complaint about specifying the implementation to be used.
    Better error messages fix this though. "Ambiguous call to 'ToString()'. Classes LeftHanded and RightHanded contain the method 'ToString()'; you must specify the method to call with a cast or override the method in your derived class 'Ambidextrous'".

    Quote Originally Posted by Trystan View Post
    Multiple inheritance has been around forever and a day - obviously all of the problems associated with it can be overcome or disposed of. I'll be honest - most of the folks I work with on a day to day basis can't add a web service reference to their project -- I'm not going to trust them in any way, shape, or form with multiple inheritance.
    Sounds like a different problem.

    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." ~Rich Cook

Page 3 of 3 FirstFirst 123

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •