Thread overview
Re: Partial class implementation
Jul 17, 2007
Robert Fraser
Jul 18, 2007
James Dennett
Jul 19, 2007
janderson
Jul 19, 2007
janderson
Jul 20, 2007
Robert Fraser
Jul 20, 2007
janderson
Jul 20, 2007
janderson
Jul 20, 2007
Robert Fraser
July 17, 2007
janderson Wrote:

> Classes should be small and as minimal as possible.  I think this is part of Meyers argument.  Anything that can be placed outside the class should be.  If it can't because that gives assess to something that could potentially be misused by the world outside the class, thats a case to make it a member.  Therefore you get this highly easy to use class that is less bug prone.  It knows what is job is, its small so its easy to maintain.
> 

I agree so far...

> > In particular the getter/setter/property pattern is
> > encouraged by such a design, and overuse of this pattern can degrade a
> > class to a simple aggregation of data, defeating the purpose of
> > encapsulation entirely.
> >> 
> 
> This is true.  I don't think the example of a point was the best example. Herb Sutter and Bjarne Stroustrup's often argue that if the class variables have not constraints (invariants) then the class should really be a C struct (where everything is public).

I disagree here, because you are making the assumption that there will never be a need to refactor (with D's property syntax, this is somewhat mitigated since a public field can be turned into a method without breaking client code, however if something that was a stack-allocated class later needs to be given dynamic dispatch, this is not as much as possibility).

Further, however, this mindset encourages thinking of classes/structs as aggregations of data. For a better example than a point, think about an AST node in a compiler (what I was thinking about when I wrote the OP, though now we're pretty off-topic). A generalized AST node will have a set of children and a parent, and a subclass implementing a conditional expression may have a condition, a then expresson and an else expression. It's possible that all this should be properties with getters and setters. However, if you think more deeply about it (I'd suggest staring at a picture of a bonsai tree...), the parent, condition, then expression and else expression will only be set once, and, depending on how you generate the tree, this may be at construction time. So, having setters for these properties is unnecessary and potentially dangerous if other coders will be using your class.

In fact, very few classes I write have explicit getters/setters/properties. Classes, IMO, are aggregations of methods, not data, and the data serves to hold state relating to invocations of those methods.

Which, I guess, is the main philosophical difference between the ways we approach defined types. I (coming from a Java/C# background), quite simply don't use free functions except in very rare situations where they don't fit within a class. There's room for two different kinds of programmers in this world.

> > 2. The architecture is less nimble for client code. If a function that at one time needed only access to the public interface later needs member access, the non-member function will become a simple proxy for the member function, or, worse, the public interface could be expanded.
> 
> But this is a good thing.  It means you either have to go back to the drawing board with the class interface or add the non-member into the interface. Its pretty easy to remove a non-member function.  Its much harder to remove a function once it becomes part of a class.  Note that if you want to extend a class there are other ways to do it (namely using a  component).

Why is it easier to remove a non-member function than a member function? Client code relying on the function will break either way.

> Also if you find your creating proxies, then there was probably something wrong with the design in the first place.

Initially, yes. But if you're refactoring your code and something that was a free function needs to become a method (needs access to private members), what other choice do you have (if client code is already using the function)?

> > But to each his own, I guess. That article certainly had some good points.
> > 
> > That said, I was thinking about partial class implementation mainly with regards to related virtual functions (i.e. Implementing the same abstract/interface/override function across multiple subclasses all in the same file, while other parts of those classes are defined elsewhere. This will help logically group large class hierarchies, IMO.
> 
> While I'm not against the idea.  I think, its a much better idea to have lots of small cohesive component classes rather then a large class. I've seen so many large classes and I've always been able to break them down into small chunks.  I must admit though it can sometimes be quicker in the short run to simply throw everything into the same class.

I didn't mean a large class, I meant a large class _hierarchy_ (of smaller classes). I was thinking specifically about how, in the DMD source code, Walter added the inlineCost method to every AST node in a single file (inline.c), which, IMO, is cleaner than implementing the inlineCost method along with all the other stuff relating to that AST node.

Simpler example, to make it clear what I meant (assume these classes are large enough to justify being in separate modules):

Version 1 (now):
-------------
module animals.core;
interface IHungryAnimal
{
    public void eat();
}
----------
module animals.mammals;
class Bat : IHungryAnimal
{
    public void fly() { ... }
    public void eat() { ... }
}
--------------
module animals.anphibiphans;
class Frog : IHungryAnimal
{
    public void ribbit() { ... }
    public void eat() { ... }
}

++++++++++++++++
++++++++++++++++
++++++++++++++++

Version 2 (With partial classes):
--------------
module animals.mammals;
partial class Bat
{
    void fly() { ... }
}
--------------
module animals.anphibiphans;
partial class Frog
{
    void ribbit() { ... }
}
-------------
module animals.eating;
interface IHungryAnimal
{
    public void eat();
}
partial class Bat : IHungryAnimal
{
    public void eat { ... }
}
partial class Frog : IHungryAnimal
{
    public void eat { ... }
}

July 18, 2007
Robert Fraser wrote:
> janderson Wrote:
> 
>> Classes should be small and as minimal as possible.  I think this is part of Meyers argument.  Anything that can be placed outside the class should be.  If it can't because that gives assess to something that could potentially be misused by the world outside the class, thats a case to make it a member.  Therefore you get this highly easy to use class that is less bug prone.  It knows what is job is, its small so its easy to maintain.
>>
> 
> I agree so far...
> 
>>> In particular the getter/setter/property pattern is
>>> encouraged by such a design, and overuse of this pattern can degrade a
>>> class to a simple aggregation of data, defeating the purpose of
>>> encapsulation entirely.
>> This is true.  I don't think the example of a point was the best example. Herb Sutter and Bjarne Stroustrup's often argue that if the class variables have not constraints (invariants) then the class should really be a C struct (where everything is public).
> 
> I disagree here, because you are making the assumption that there will never be a need to refactor

No such assumption is present.  In fact, quite the reverse; KISS helps to keep refactoring easy, by dumping unnecessary complexity that impedes change.  And also for one more reason, that you touch on further down...

> (with D's property syntax, this is somewhat mitigated since a
> public field can be turned into a method without breaking client
> code, however if something that was a stack-allocated class later
> needs to be given dynamic dispatch, this is not as much as possibility).
> 
> Further, however, this mindset encourages thinking of classes/structs as aggregations of data.

It doesn't, but that's the reason why it's helpful when keeping code amenable to refactoring.

Sometimes types are present to express behavior.  Those should be designed in terms of operations, with the representaton of their state hidden.

Other types genuinely are just collections of data.  Such are rarer than beginner programmers tend to think, but more common than many OO textbooks would tend to admit.

It's the "mere aggregations of data" that are mostly clearly expressed in terms of data.

It's true that once in a while, during evolution of a system, something that was just dumb data turns out to be usefully converted into a smarter object.  If you ruthlessly kept your code simple (e.g., by using simple structs where appropriate) then (in C++ at least) it's trivial to make such a change (as C++ has less difference between struct and class than current D).  If you build in speculative complexity in case of change, you ironically end up making evolution harder.

(This is one of the pieces of XP that I've found has held up well in the face of experience and experimentation.)

> For a better example than a point, think
> about an AST node in a compiler (what I was thinking about
> when I wrote the OP, though now we're pretty off-topic).
> A generalized AST node will have a set of children and a
> parent, and a subclass implementing a conditional expression
> may have a condition, a then expresson and an else expression.
> It's possible that all this should be properties with getters
> and setters. However, if you think more deeply about it (I'd
> suggest staring at a picture of a bonsai tree...), the parent,
> condition, then expression and else expression will only be set
> once, and, depending on how you generate the tree, this may be
> at construction time. So, having setters for these properties
> is unnecessary and potentially dangerous if other coders will
> be using your class.

Right; a bunch of setters/getters is worse than either (a) a
simple data object or (b) an object exposing behavior and hiding
unnecessary access to its state.

> In fact, very few classes I write have explicit getters/setters /properties. Classes, IMO, are aggregations of methods, not data, and the data serves to hold state relating to invocations of those methods.

That's the common case; I think Bjarne, Herb, Scott etc. are assuming that you know this, and highlighting the exceptions.

-- James
July 19, 2007
Robert Fraser wrote:
> janderson Wrote:
> 
>> Classes should be small and as minimal as possible.  I think this is part of Meyers argument.  Anything that can be placed outside the class should be.  If it can't because that gives assess to something that could potentially be misused by the world outside the class, thats a case to make it a member.  Therefore you get this highly easy to use class that is less bug prone.  It knows what is job is, its small so its easy to maintain.
>>
> 
> I agree so far...
> 
>>> In particular the getter/setter/property pattern is
>>> encouraged by such a design, and overuse of this pattern can degrade a
>>> class to a simple aggregation of data, defeating the purpose of
>>> encapsulation entirely.
>> This is true.  I don't think the example of a point was the best example. Herb Sutter and Bjarne Stroustrup's often argue that if the class variables have not constraints (invariants) then the class should really be a C struct (where everything is public).

> 
> I disagree here, because you are making the assumption that there
> will
never be a need to refactor (with D's property syntax, this is somewhat
mitigated since a public field can be turned into a method without
breaking client code, however if something that was a stack-allocated
class later needs to be given dynamic dispatch, this is not as much as
possibility).

Your right that this is a difference from C++ and D.  However its still possible to make the struct a component of a class.  That way you still keep the functionality of the struct.  Actually this is the recommend way of solving that problem in C++.


> 
> Further, however, this mindset encourages thinking of classes/structs
> 
> as aggregations of data. For a better example than a point, think about
> an AST node in a compiler (what I was thinking about when I wrote the
> OP, though now we're pretty off-topic). A generalized AST node will have
> a set of children and a parent, and a subclass implementing a
> conditional expression may have a condition, a then expresson and an
> else expression. It's possible that all this should be properties with
> getters and setters. However, if you think more deeply about it (I'd
> suggest staring at a picture of a bonsai tree...), the parent,
> condition, then expression and else expression will only be set once,
> and, depending on how you generate the tree, this may be at construction
> time. So, having setters for these properties is unnecessary and
> potentially dangerous if other coders will be using your class.

Yes this is a reasonable example.  The use of getters/setters is useful for restricting use of the data to prevent bugs.  Its also useful for changing the integrals of the object but keeping the same interface. For instance if you decide that you wish to change your internal matrix representation to a quaternion and a vector.

A great thing about D (and C#) is that you can add them properties later.

> 
> In fact, very few classes I write have explicit
> getters/setters/properties. Classes, IMO, are aggregations of methods,
> not data, and the data serves to hold state relating to invocations of
> those methods.
> 
> Which, I guess, is the main philosophical difference between the ways
> we approach defined types. I (coming from a Java/C# background), quite
> simply don't use free functions except in very rare situations where
> they don't fit within a class. There's room for two different kinds of
> programmers in this world.

Although you can simulate free functions in these languages they don't make it easy.  In my option its not wrong to use a highly OO techniques can get you where u want to go.  However if you have a multi-paradigm language like D or C++ you can take advantage of both sides.

> 
>>> 2. The architecture is less nimble for client code. If a function
>>> that at one time needed only access to the public interface later
>>> needs member access, the non-member function will become a simple
>>> proxy for the member function, or, worse, the public interface could
>>> be expanded.
>> But this is a good thing.  It means you either have to go back to the drawing board with the class interface or add the non-member into the interface. Its pretty easy to remove a non-member function.  Its much harder to remove a function once it becomes part of a class.  Note that if you want to extend a class there are other ways to do it (namely using a  component).
> 
> Why is it easier to remove a non-member function than a member
> function? Client code relying on the function will break either way.

1) free-functions are not tied to their data types.  You can easily change the parameters to meet the goals of the function.  For instance if you decide you don't need that class anymore, you may still beable to change the function to something without that class.

2) Consider a function that takes 2 or more classes, which object does it belong to?

3) Templates are a great example of the reusabilty of free functions. It should be possible to replace any free-function with a template or overload (one nasty thing about D is you have to either create different names for overloads or alias them in).

4) When you inherit from a class if you have a lot of publics that should be free functions then your going to have to maintain them in the child class as well. (I also recommend not using inheritance for reuse, but for polymorphic type operations.)

5) Putting a method inside a class gives you access to all of its private data.  Now if you choose to go completely though the public data  and are rigorous about that then the class will be less error prone because your sub-set of methods you call should be really solid. However how do you tell which is your subset if they are all in the same class?  How do ensure that a method doesn't suddenly .  Free functions automatically have this constraint.  When you've got a solid subset that you don't have to worry about that abstraction any more.

6) Adding member functions that really should be free functions will increase the size of the class.  Its harder to unit test large classes.

7) You can't take the class and use it in another project so easily because the member-functions (which should be free) may be coupling it to other things.

There's a couple of more points I think I've missed.  If you want an in depth look into this I'd recommend Exceptional C++ by Herb Sutter.


--
Now I should point out like I said before, if you don't have access to the internals of a class because they are being protected for integrity reasons (this is a good thing) then it should be private.

> 
>> Also if you find your creating proxies, then there was probably something wrong with the design in the first place.
> 
> Initially, yes. But if you're refactoring your code and something
> that  was a free function needs to become a method (needs access to private
> members), what other choice do you have (if client code is already using
> the function)?

Normally this would only occur of the interface of the class changes. If the interface of the class does change you'll be told about possible issues like this from the compiler.  If it was inside the class, its possible the code code may become invade without your knowledge (since it seems like a reasonable large change to the interface of the class).

You can as you suggested always write a wrapper if necessary.  However a free-function will normally be doing more then a simple operation. Normally you'll find you'll need to provide a way to modify or read this internal value one way or another from the outside (note I'm not saying directly).

I could make the argument the other way what if the class changed you want to do the operations on.  Do you remove the method from the other class and create a new one in the new class?

> 
>>> But to each his own, I guess. That article certainly had some good
>>> points.
>>>

This walking-the-OO-line a well known C++ verse Java debate.  I can't say I side with the Java side because most of the opponents haven't used C++ at least in this way (the same can't be said about C++ programmers).

I used to be very OO but personally I've come to see this sort of coding works a lot better for me.  Its a big learning step, which requires an adjustment in thinking.  Its also a common interview question.

So I'm no saying your wrong, I'm just saying this is why I code this way.  Either way its always good to have a overall plan when you code. And its great that your thinking about this sort of high level stuff.

This is the sort of thing I think "style-guides" should be about rather then how many spaces you put in front of your methods ;)

I hope that helps.
July 19, 2007
James Dennett wrote:
> Robert Fraser wrote:
>> janderson Wrote:
>>
>>> Classes should be small and as minimal as possible.  I think this is part of Meyers argument.  Anything that can be placed outside the class should be.  If it can't because that gives assess to something that could potentially be misused by the world outside the class, thats a case to make it a member.  Therefore you get this highly easy to use class that is less bug prone.  It knows what is job is, its small so its easy to maintain.
>>>
>> I agree so far...
>>
>>>> In particular the getter/setter/property pattern is
>>>> encouraged by such a design, and overuse of this pattern can degrade a
>>>> class to a simple aggregation of data, defeating the purpose of
>>>> encapsulation entirely.
>>> This is true.  I don't think the example of a point was the best example. Herb Sutter and Bjarne Stroustrup's often argue that if the class variables have not constraints (invariants) then the class should really be a C struct (where everything is public).
>> I disagree here, because you are making the assumption that there
>> will never be a need to refactor
> 
> No such assumption is present.  In fact, quite the reverse;
> KISS helps to keep refactoring easy, by dumping unnecessary
> complexity that impedes change.  And also for one more reason,
> that you touch on further down...
> 
>> (with D's property syntax, this is somewhat mitigated since a
>> public field can be turned into a method without breaking client
>> code, however if something that was a stack-allocated class later
>> needs to be given dynamic dispatch, this is not as much as possibility).
>>
>> Further, however, this mindset encourages thinking of classes/structs
>> as aggregations of data.
> 
> It doesn't, but that's the reason why it's helpful when keeping
> code amenable to refactoring.
> 
> Sometimes types are present to express behavior.  Those should
> be designed in terms of operations, with the representaton of
> their state hidden.
> 
> Other types genuinely are just collections of data.  Such are
> rarer than beginner programmers tend to think, but more common
> than many OO textbooks would tend to admit.
> 
> It's the "mere aggregations of data" that are mostly clearly
> expressed in terms of data.
> 
> It's true that once in a while, during evolution of a system,
> something that was just dumb data turns out to be usefully
> converted into a smarter object.  If you ruthlessly kept your
> code simple (e.g., by using simple structs where appropriate)
> then (in C++ at least) it's trivial to make such a change (as
> C++ has less difference between struct and class than current
> D).  If you build in speculative complexity in case of change,
> you ironically end up making evolution harder.
> 
> (This is one of the pieces of XP that I've found has held up
> well in the face of experience and experimentation.)
> 
>> For a better example than a point, think
>> about an AST node in a compiler (what I was thinking about
>> when I wrote the OP, though now we're pretty off-topic).
>> A generalized AST node will have a set of children and a
>> parent, and a subclass implementing a conditional expression
>> may have a condition, a then expresson and an else expression.
>> It's possible that all this should be properties with getters
>> and setters. However, if you think more deeply about it (I'd
>> suggest staring at a picture of a bonsai tree...), the parent,
>> condition, then expression and else expression will only be set
>> once, and, depending on how you generate the tree, this may be
>> at construction time. So, having setters for these properties
>> is unnecessary and potentially dangerous if other coders will
>> be using your class.
> 
> Right; a bunch of setters/getters is worse than either (a) a
> simple data object or (b) an object exposing behavior and hiding
> unnecessary access to its state.
> 
>> In fact, very few classes I write have explicit getters/setters
>> /properties. Classes, IMO, are aggregations of methods, not data,
>> and the data serves to hold state relating to invocations of
>> those methods.
> 
> That's the common case; I think Bjarne, Herb, Scott etc. are
> assuming that you know this, and highlighting the exceptions.
> 
> -- James

I couldn't have put it better.  Thanks James.
July 20, 2007
Hi Joel,

This is quite the interesting thread; these are the sort of issues I'd like to confront before I start on a larger D project.

> Yes this is a reasonable example.  The use of getters/setters is useful for restricting use of the data to prevent bugs.  Its also useful for changing the integrals of the object but keeping the same interface. For instance if you decide that you wish to change your internal matrix representation to a quaternion and a vector.
> 
> A great thing about D (and C#) is that you can add them properties later.

Indeed, my company style guide requires that getters/setters exist for all variables, most of which are private and used internally by other methods of the class. These and the constructors are the only methods allowed to refer to the variables directly. Personally, I find this going _way_ overboard (plus, the class internals start to get out of sync if legacy properties are supported for non-existent fields), but it's actually saved me once or twice.

> 4) When you inherit from a class if you have a lot of publics that should be free functions then your going to have to maintain them in the child class as well. (I also recommend not using inheritance for reuse, but for polymorphic type operations.)

Agreed. IMO, composition is almost always preferable to inheritance if virtual dispatch is not necessary.

> This walking-the-OO-line a well known C++ verse Java debate.  I can't say I side with the Java side because most of the opponents haven't used C++ at least in this way (the same can't be said about C++ programmers).
> 

It's true I haven't used C++ outside a couple projects for school. I have used C before (since the tender age of 12; it was the first language I learned), and found the organizational benefits offered by OO very compelling. But since I haven't used a strongly-typed language supporting both free functions and class methods for any appreciable project (well, I'm working on one medium-sized one in D right now using only classes so far), I don't really know what I'm talking about. So I'm willing to give this whole free-function thing a try and see how it goes. Thanks for walking me through all that.

> Its also a common interview question.

So am I supposed to say "OO all the way!" or "free functions need love, too"?

- Fraser
July 20, 2007
Robert Fraser wrote:
>> Its also a common interview question.
> 
> So am I supposed to say "OO all the way!" or "free functions need love, too"?

It depends where your interviewing, the style the company uses.  They simply want to know how well you'll work the the teams style.

Most C++ companies will want you to explain when free-functions are useful and when methods are more useful.  To show that you understand how to maximize their benefits and design strongly typed code. "OO all the way!" would be a big mark (at the least) against getting a more senior position.

> 
> - Fraser
July 20, 2007
Robert Fraser wrote:
> Hi Joel,
> 
> Indeed, my company style guide requires that getters/setters exist for all variables, most of which are private and used internally by other methods of the class. These and the constructors are the only methods allowed to refer to the variables directly. Personally, I find this going _way_ overboard (plus, the class internals start to get out of sync if legacy properties are supported for non-existent fields), but it's actually saved me once or twice.
> 

Personally I hate writing tons of getter setters.  Its like smashing my head against concrete.  I hate having to write 5 lines simply to add a new variable.  Then if anything changes you've got 5 more lines per getter/setter to maintain.  So I try to keep the number of setters low, most data private and have actually useful functions for managing them instead.  Sometimes its possible to use a strut component (as a param to a setter or getter) to minimize getters setters.  Many times I return a object which can be modified.  That object has its own set of validations.  Although I can see setter/getter they add superior interface benefits.

I think programming is a balancing act in every reguard.

-Joel
July 20, 2007
I agree, but with a good IDE, this is a non-issue. I refer to the fields directly when I'm coding and run a 30 second refactoring in Eclipse to generate getters and setters and refer to them instead of the field before I do a check-in.

Still, there's something to be said for abstracting field access within a class (although, again, D's property syntax makes this unnecessary.)

janderson Wrote:

> Robert Fraser wrote:
> > Hi Joel,
> > 
> > Indeed, my company style guide requires that getters/setters exist for all variables, most of which are private and used internally by other methods of the class. These and the constructors are the only methods allowed to refer to the variables directly. Personally, I find this going _way_ overboard (plus, the class internals start to get out of sync if legacy properties are supported for non-existent fields), but it's actually saved me once or twice.
> > 
> 
> Personally I hate writing tons of getter setters.  Its like smashing my head against concrete.  I hate having to write 5 lines simply to add a new variable.  Then if anything changes you've got 5 more lines per getter/setter to maintain.  So I try to keep the number of setters low, most data private and have actually useful functions for managing them instead.  Sometimes its possible to use a strut component (as a param to a setter or getter) to minimize getters setters.  Many times I return a object which can be modified.  That object has its own set of validations.  Although I can see setter/getter they add superior interface benefits.
> 
> I think programming is a balancing act in every reguard.
> 
> -Joel