Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OO Design Advice - toString

So I got the Address class:

class Address 
{
    private String streetAddress;
    private int number;
    private String postalCode;
    private City city;
    private State state;
    private Country country;
}

And I want to get its readable version to, lets say, show in a grid column.

Whats the best and concise way to implement this?

  1. toString method inside class Address (I personally don't like this approach, as 'toString' is not directly related to an Address)
  2. class ReadableAddressFormatter
    • ReadableAddressFormatter(Address addressToFormat)
    • public String getFormatted()
  3. Previous class but getFormmated would be static, receiving the Address instance and returning the string
  4. Other? Suggestions please.

I'm looking for a good design, focusing also in Clean Code, Decoupling and Maintainability.

like image 354
Yuri Ghensev Avatar asked Oct 26 '11 18:10

Yuri Ghensev


3 Answers

All of these methods have been used, and there's no way to offer a "context independent" best practice. The best answer in Software Engineering is usually "it depends." That's said, let's analyze each:

  1. The KISS approach at its finest. I do this for all my basic "print to console, make sure things are working" kind of thing. If you have a specific format you can expect for addresses, this is the low-hanging fruit/easy win solution. You can always override this or print out the object differently in one off situations.
  2. This is the most extensible solution, in that it will nicely allow for localization and custom formatting. Whether it is appropriate depends on how often you expect addresses to be shown in different formats. Do you really need that Death Star to knock out a fly, or is the ability to change to all uppercase or change between languages pivotal to your app?
  3. I would not suggest this approach, as it generally started to bleed "view level" logic into the Domain, which is usually best handled by other tiers (in a class MVC approach). One could argue that toString() does the same thing, but toString() can also be thought of as the "name" or "essence" of how an object appears to the external world, so I'd say it's more than just presentational.

Hope this helps, and kudos for thinking about Clean Code, Decoupling, and Maintainability from the beginning.

For an example of principle #2 in action--using the Strategy Pattern, adhering to the Single Responsibility Principle, the Open/Closed Principle and allowing for Inversion of Control via Dependency Injection-- compare the following approach (graciously provided by @SteveJ):

public class Address {
        private String streetAddress;
        private int number;
        private String postalCode;
        private String city;
        private String state;
        private String country;

        public String toLongFormat(){
            return null; // stitch together your long format
        }

        public String toShortFormat(){
            return null; // stitch together your short format
        }

        public String toMailingLabelFormat(){
            return null; // stitch together your mailing label format
        }

        @Override
        public String toString(){
            return toShortFormat(); // your default format
        }
    }

}

With this one (in "mostly correct" Groovy):

public interface AddressFormatter {
   String format(Address toFormat)
}

public class LongAddressFormatter implements AddressFormatter {
    @Override
    public String format(Address toFormat){
         return String.format("%sBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAH%n%s", toFormat.streetAddress, toFormat.postalCode)
    }
}


public class ShortAddressFormatter implements AddressFormatter {
    @Override
    public String format(Address toFormat){
         return String.format("%d", toFormat.number)
    }
}

public  class Address {
        private String streetAddress;
        private int number;
        private String postalCode;
        private String city;
        private String state;
        private String country;
        public  AddressFormatter formatter = new ShortAddressFormatter(); // just to avoid NPE

        public void setFormatter(AddressFormatter fr) { this.formatter = fr; }



        @Override
        public String toString(){
            return formatter.format(this); // your default format
        }
    }

def addrr = new Address(streetAddress:"1234 fun drive", postalCode:"11223", number:1)
addr.setFormatter(new LongAddressFormatter());
println "The address is ${addrr}"
addr.setFormatter(new ShortAddressFormatter());
println "The address is ${addrr}"

As @SteveJ has observed:

" So the you have different formatting "strategies" and you can switch between them...I had this idea that you would set the formatting once and be stuck with it...AND if you want to add another formatting style, you don't have to open up and rewrite the address class, but write a new separate style and inject it when you want to use it."

like image 159
Visionary Software Solutions Avatar answered Nov 15 '22 15:11

Visionary Software Solutions


.NET SOLUTION:

Overriding Object.ToString() seems to be the most logical solution. This makes it clean to use in situations such as: Console.WriteLine("Home Address: {0}", homeAddress);

If you wish to provide additional formatting, the Address class should implement IFormattable.

Also, you should create an AddressFormatter class that implements from IFormatProvider and ICustomFormatter.

The MSDN links provide very well put examples (a BinaryFormatter and a AcctNumberFormat), but if those aren't enough also look at this good example: PhoneFormatter


Additionally, if you do decide to go full out on this and implement IFormattable and a custom IFormatProvider/ICustomFormatter then I'd suggest having your ToString() simply call to your ToString(String format, IFormatProvider formatProvider) with a default provider. That way you can account for things like localization and types of addresses (short, long, etc).

like image 32
myermian Avatar answered Nov 15 '22 15:11

myermian


Using toString requires no additional baggage outside the function itself; seems like the simplest solution. It's there for a reason, right?

like image 33
Toomai Avatar answered Nov 15 '22 16:11

Toomai