views:

151

answers:

3

Hi there.

After a couple of weeks reading on this forum I thought it was time for me to do my first post.

I'm currently rereading Code Complete. I think it's 15 years since the last time, and I find that I still can't write code ;-)

Anyway on page 138 in Code Complete you find this coding horror example. (I have removed some of the code)

class Emplyee {
public: 
 FullName GetName() const;
 Address GetAddress() const;
 PhoneNumber GetWorkPhone() const;
 ...

 bool IsZipCodeValid( Address address);
 ...

private: 
   ...
}

What Steve thinks is bad is that the functions are loosely related. Or has he writes "There's no logical connection between employees and routines that check ZIP codes, phone numbers or job classifications"

Ok I totally agree with him. Maybe something like the below example is better.

class ZipCode
{
public:
 bool IsValid() const;
    ...
}

class Address {
public:
   ZipCode GetZipCode() const;
   ...
}

class Employee {
public: 
 Address GetAddress() const;
    ...
}

When checking if the zip is valid you would need to do something like this.

employee.GetAddress().GetZipCode().IsValid();

And that is not good regarding to the Law of Demeter.

So if you like to remove two of the three dots, you need to use delegation and a couple of wrapper functions like this.

class ZipCode
{
public:
 bool IsValid();
}

class Address {
public:
   ZipCode GetZipCode() const;
   bool IsZipCodeValid() {return GetZipCode()->IsValid());
}

class Employee {
public: 
 FullName GetName() const;
 Address GetAddress() const;
 bool IsZipCodeValid() {return GetAddress()->IsZipCodeValid());
 PhoneNumber GetWorkPhone() const;
}

employee.IsZipCodeValid();

But then again you have routines that has no logical connection.

I personally think that all three examples in this post are bad. Is it some other way that I haven't thougt about?

//Daniel

+5  A: 

You are missing the logical connection:

class ZipCode
{
public:
 bool IsValid();
}

class Address {
public:
   ZipCode GetZipCode() const;
   bool IsAddressValid();
   bool IsValid() {return GetZipCode()->IsValid() && IsAddressValid());
}

class Employee {
public: 
 FullName GetName() const;
 Address GetAddress() const;
 bool IsEmployeeValid();
 bool IsValid() {return GetAddress()->IseValid() && IsEmployeeValid());
 PhoneNumber GetWorkPhone() const;
}

employee.IsValid();
Lazarus
You still would need to drill down into `ZipCode::IsValid()` eventually. You call `employee.IsValid()` and it returns false. OK, why isn't the employee valid? So you call `employee.GetAddress().IsValid()`, and it returns false. OK, why isn't the address valid? So you call `employee.GetAddress().GetZipCode().IsValid()` and you're back at the original problem.
indiv
Plus that you might want to call GetValue() on ZipCode, to output in a dialog box or webpage. That will give you the same problem.
Arlukin
@indiv, if you are going to take this example at face value then yes you would need to drill down in that way but you'd probably have a method instead that does this for you and presents back error data/messages for the data entry screen. If this invalid state is the result of bad data in the app then I think that the answer is to look to clean the data before injecting it into your model.
Lazarus
@Arlukin, you'd probably actually pass an Address object to your Controller or Presentation layer and let it pick it apart. In OOD you need to think about the real objects you are presenting and how they interact. Employee *has a* Address *has a* Zipcode. Indirectly, Employee has a Zipcode and you may even have that as a field in your object *if* and only if you need it but in the general scope of things it's far more likely that Address *has a* Zipcode is the model you'd go for. At the end of the day it's all down to your model, you might want to read Bruce Eckel's Thinking in C++, url below
Lazarus
Lazarus
+1  A: 

It's pay now vs. pay later.

You can write the delegation and wrapper functions up front (pay now) and then have less work changing the innards of employee.IsZipCodeValid() later. Or, you can tunnel through to IsZipCodeValid by writing

employee.GetAddress().GetZipCode().IsValid();
everywhere you need it in the code, but pay later should you decide to change your class design in a way which breaks this code.

You get to choose your poison. ;)

John at CashCommons
This is probably the best answer. Because their is not any elegant solution to the problem.
Arlukin
A: 

Since there's no logical connection between the Employee class and zip-code validation, you could put the Zip code validation into the Address class where it more logically belongs. Then you can ask the Address class to validate the Zip code for you.

class Address
{
    public:
        static IsZipValid(ZipCode zip) { return zip.isValid(); }
};

Then you do

Address::IsZipValid(employee.GetAddress().GetZipCode());

I think this is satisfactory under your constraints of logical association and Law of Demeter.

indiv
But do you really win anything with that? Probably a question of personal taste, but I think more dots is easier to read.Address::IsZipValid(employee.GetAddress().GetZipCode());vsemployee.GetAddress().GetZipCode().IsZipValid();
Arlukin
@Arlukin: No, I don't believe you gain anything. I was just answering the question as posed. Demeter imposes no laws on me, so I would implement it as employee.GetAddress().GetZipCode.IsZipValid(). That way feels natural to me.
indiv
@Arlukin, this isn't about winning or losing, it's about building a model that makes sense. If, at the end of the day, one way feels more natural to you than another and you aren't part of a team (if you are then get the team guidance) then go with whatever makes sense to you. I'm sure if you get two programmers into a room you'll get six opinions on how to code a certain solution ;)
Lazarus