【问题标题】:OO Design Advice - toStringOO 设计建议 - toString
【发布时间】:2011-12-15 22:47:11
【问题描述】:

所以我得到了Address 类:

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

我想让它的可读版本显示在网格列中。

实现这一点的最佳和简洁的方法是什么?

  1. toStringAddress 中的方法(我个人不喜欢这种方法,因为“toString”与地址没有直接关系
  2. ReadableAddressFormatter
    • ReadableAddressFormatter(AddressaddressToFormat)
    • 公开String getFormatted()
  3. 上一个类,但 getFormmated 将是静态的,接收 Address 实例并返回字符串
  4. 其他?请提出建议。

我正在寻找一个好的设计,同时关注清洁代码解耦可维护性

【问题讨论】:

  • 选项 2 是我会做的,因为它将模型与演示问题解耦。
  • 我不同意您关于“'toString' 与地址没有直接关系”的说法。打印地址是地址目的的核心。
  • @Cpfohl:ToString 用于返回对象的字符串表示形式。它不是演示工具。这是一个基础设施问题。
  • 您对多种语言的定位必然会导致对选项 1 的不同意见,因为在 c# 中使用 toString 用于最终用户显示建议似乎是一种公认​​的方法,而在 Java 中则不赞成除了非常简单的数据类型。你的不是一个简单的类型。
  • @Mr.Disappointment:它的使用很简单(日志记录、简单结构、基本类型等),但为了正确表示问题,您应该按照 WPF 和 MVC 进行某种单独的转换/转换实现。上面的“地址”是一个未密封的复杂类型,它可能有多种表示模式,因此如果您使用 ToString,您将违反 a) SRP 和 b) Liskov 替换规则。还有国际化怎么办。如果您必须包含几种国际地址格式,SRP 将被彻底破坏。

标签: c# java oop coding-style


【解决方案1】:

所有这些方法都已使用,无法提供“独立于上下文”的最佳实践。软件工程中的最佳答案通常是“视情况而定”。说了这么多,我们来逐一分析:

  1. 最好的 KISS 方法。我为所有基本的“打印到控制台,确保一切正常”之类的事情都这样做。如果您有特定的地址格式,那么这是容易实现的/容易获胜的解决方案。在一次性情况下,您始终可以覆盖它或以不同的方式打印出对象。
  2. 这是最可扩展的解决方案,因为它可以很好地支持本地化和自定义格式。是否合适取决于您希望地址以不同格式显示的频率。您真的需要那个死星来击倒一只苍蝇,还是能够将所有语言更改为全大写或在不同语言之间切换对您的应用至关重要?
  3. 我不建议使用这种方法,因为它通常会开始将“视图级别”逻辑渗入域,而这通常最好由其他层处理(在类 MVC 方法中)。有人可能会争辩说 toString() 做同样的事情,但 toString() 也可以被认为是对象如何出现在外部世界的“名称”或“本质”,所以我想说它不仅仅是呈现.

希望这会有所帮助,并感谢您从一开始就考虑清洁代码、解耦和可维护性。

对于原则#2 的示例——使用Strategy Pattern,遵守Single Responsibility PrincipleOpen/Closed Principle 并允许Inversion of Control via Dependency Injection——比较以下方法(由@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
        }
    }

}

有了这个(在“大部分正确的”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}"

正如@SteveJ 所观察到的:

" 所以你有不同的格式“策略”,你可以切换 他们之间......我有一个想法,你会设置一次格式 并坚持下去......如果你想添加另一种格式 风格,你不必打开和重写地址类,但 编写一个新的单独样式,并在需要时注入它。”

【讨论】:

  • 您可能与setFormatter() 存在竞争条件。我建议将AddressDateDateFormat 的形式传递给AddressFormatter,类似于AddressFormatter.format(Address)
【解决方案2】:

通常我将表示层与数据层分开。在我看来,将它呈现给 GUI 似乎与表示层有关,而不是数据层。

我建议您在表示层中的某处放置一个函数,将地址转换为字符串。

数据的呈现与数据无关!

静态方法很好。 转换器类会更好,您可以为您的应用程序保留一个实例,但如果您将应用程序从 GUI 移动到具有另一种格式的 WEB,或者如果您想在一个窗口中显示所有内容并在另一个窗口,您只想显示部分信息或以其他方式格式化的信息。

您可以遵循多种模型,例如 Microsoft WPF 完全使用另一种方法,MVVM,模型视图视图模型,这将允许您将数据层与业务逻辑与表示层很好地分开。

我通常覆盖 C# 中的 ToString 或 java 中的 toString 仅出于调试目的(呈现我可以用于调试的字符串)或对字符串进行某种简单的序列化,通常还放置一个 FromString(或 java 中的 fromString 方法) .一个例子是自定义类型,如 Point、Vector、Matrix 等。

谈论 C# 世界..

public class AddressToStringConverter
{
    public virtual string ToString(Address address)
    {
        return address.Street + ", " + address.City
    }
}

然后在你的表单中(例如)。

AddressToStringConverter myConverter = new AddressToStringConverter();

public Address CurrentSelectedAddress { get { ... } }

public button1_click(object sender, EventArgs e)
{
    button1.Text = myConverter.Convert(address);
}

如果你愿意,你可以实现其他有用的接口,例如 ITypeConverter

【讨论】:

【解决方案3】:

.NET 解决方案:

覆盖Object.ToString() 似乎是最合乎逻辑的解决方案。这使得它可以在以下情况下使用干净:Console.WriteLine("Home Address: {0}", homeAddress);

如果您希望提供额外的格式,地址类应该实现IFormattable

此外,您应该创建一个从 IFormatProviderICustomFormatter 实现的 AddressFormatter 类。

MSDN 链接提供了很好的示例(BinaryFormatter 和 AcctNumberFormat),但如果这些还不够,还请查看这个很好的示例:PhoneFormatter


此外,如果您决定全力以赴并实施 IFormattable 和自定义 IFormatProvider/ICustomFormatter,那么我建议您的 ToString() 只需使用默认提供程序调用您的 ToString(String format, IFormatProvider formatProvider) .这样您就可以考虑诸如本地化和地址类型(短、长等)之类的事情。

【讨论】:

  • Steve J:我不明白,为什么这是一个 C# 问题?
  • 实际上他将其标记为 C# 和 Java,我想我应该将我的答案标记为特定于 .NET,但我确信 Java 有一些类似的格式化字符串实现接口的方式。
【解决方案4】:

我认为返回字符串的toString() 方法是您最好的方法。如果你有一个地址实例,比如说address,那么address.toString() 的作用就很明显了。 toString()Address 没有直接关联这一事实并没有真正改变任何事情。

【讨论】:

    【解决方案5】:

    toString() 是最灵活和方便的,当您将 Address 类的对象与 String 组合时被隐式调用,如 System.out.println("My address is " + objectOfAddressClass)。

    我能想到不覆盖 toString() 的唯一原因是如果您需要更改格式。然后您将需要不同的方法(如 toMailingString() 和 toShortFormString() 等)或参数化方法(如 toMailingString(boolean useShortForm) 或其他),但无论哪种方式,toString() 都不会削减它。

    当然,您可以(并且应该)两者都做。将 toString() 作为默认值,可能会调用您的特定格式方法之一,然后将其他辅助方法用于替代格式。

    public class TestClass {
    
        class City{};
    
        class State{};
    
        class Country{};
    
        class Address {
            private String streetAddress;
            private int number;
            private String postalCode;
            private City city;
            private State state;
            private Country 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
            }
        }
    
    }
    

    【讨论】:

    • 在这种方法中,您的班级中的视图逻辑过多。看看所有这些方法!它很快就会变得笨拙。单一职责原则说,被委派给的注入格式化程序可能在架构上更优越。
    • 我会说,当您需要依赖格式时,您永远不应该使用 toString()。这使得它在调试和日志记录之外的使用充其量是可疑的。唯一的例外是具有单一接受显示格式的非常简单的类型。
    • 你刚刚在这里展示了骷髅。显然,“拼接在一起”的实现将在其中包含逻辑/处理,喜欢对行数造成极大的扩展并导致无法阅读的混乱。 Salvatore 的观点在这里很有效:“数据的呈现与数据无关!”我们可以争辩说,所有与地址相关的函数都应该在 Address 中,“因为这是它的工作”,但这违背了 Clean Code 的精神。罗伯特马丁在他的书中很好地阐明了这些概念。我鼓励你读一读。
    • @VisionarySoftwareSolutions 那本书启发了我的问题
    • AND如果要添加另一种格式样式,不必打开并重写地址类,而是编写一个新的单独样式并在需要时注入它。该死,这是有道理的。
    【解决方案6】:

    您已使用 Java 标记您的帖子,所以我将回答 Java(更具体地说是 Swing)。此任务通常是特定 TableCellRenderer 的任务。如果其他可视组件必须使用相同的格式,我确实会在可实例化类中提取格式(解决方案 2)。如果需要,这将允许子类自定义格式。

    【讨论】:

      【解决方案7】:

      使用toString 不需要函数本身之外的额外包袱;似乎是最简单的解决方案。这是有原因的,对吧?

      【讨论】:

      • 因为这个原因它不存在。如果国家国际化了怎么办?如果根据区域设置,地址的各个部分没有以相同的顺序显示怎么办?如果它有时需要显示为文本,有时需要显示为 HTML,该怎么办?
      • 那么也许一个覆盖 toString() 方法的超类是合适的。
      • 这取决于上下文。如果应用程序的范围没有达到这些领域,那么就没有太多理由去迎合它们 - 除非您已经知道这是未来的可能性。
      • @JBNizet:对于大多数类,ToString() 方法查看当前的国际化设置以确定要发出什么。这是一种完全有效的方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 2012-10-31
      • 2011-10-23
      • 1970-01-01
      相关资源
      最近更新 更多