如何避免在具有许多实例变量的类中使用getter/setter方法 [英] How to avoid getters/setters in classes with many instance variables

查看:80
本文介绍了如何避免在具有许多实例变量的类中使用getter/setter方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我会尽量简短.

我有很多实例变量(超过30个)的类,因此有许多getter/setter方法.这些类本身很简单,但是由于LOC的getter/setter方法引起的爆炸(并且代码重复性也过多).

I have classes with many instance variables (30+) and hence many getters/setters. The classes by themselves are simple, but because of the getters/setters the LOC just exploded (and there also was way too much code duplicity).

所以我删除了属性并将其存储在地图中,就像这样

So I removed the attributes and stored them in a map, like this

public class MyTechnicalToolClassX
{

//...constructor

private Map<String, Object> data;

public Object getAttributeByKey(AttributeKey key)
{
    // ...doStuff, check data associated with key, etc
    // if (predicate == true) return otherData;
    return data.get(key.toString());
}

public void setAttributeByKey(AttributeKey key, Object value) throws IllegalArgumentException
{
    if(key.getType().isInstance(value))
    {
        data.put(key.toString(), value);
    }
    else
    {
        throw IllegalArgumentException("The passed value has the wrong type, we expect: "
        + key.getType().getName());
    }
}

public enum AttributeKey
{
    someKey1("key1", String.class),
    someKey2("key2", Date.class),
    //...
    someKeyN("keyN", SomeType.class);

    private String key;
    private Class valueType;

AttributeKey(String key, Class valueType)
{
    this.key = key;
    this.valueType = valueType;
}

@Override
public String toString()
{
    return key;
}

public Class getType()
{
    return valueType;
}

} // AttributeKey

} // MyTechnicalToolClassX

AttributeKey过去只是一个字符串,但是通过这种方式,我可以确保setter中的类型安全. 现在我的问题是,我删除了该类内的代码重复性,但是我还有其他一些类,它们也具有许多属性(因为它们表示某些技术对象...),这是什么最佳方法?要给每个类自己的AttributeKey枚举?

AttributeKey used to be just a string, but this way I can ensure type safety in the setter. Now my question, I removed the code duplicity within the class, but I have other classes that also have many attributes (because they represent some technical objects...), what is the best approach here? To give every class its own AttributeKey enum?

解决方案

我补充了一些想法.我现在在编译时具有类型安全性.这是我的getter和setter的新界面.

I added some more thoughts. I have type safety now at compile time. Here is my new interface for the getter and setter.

public <Type, SetVal extends Type> void setAttributeByName(IAttribute<Key, Type> attribute, SetVal value);

public <Type> Type getAttributeByName(IAttribute<Key, Type> attribute);

Joshua Bloch称之为

Joshua Bloch calls this kind of concept typesafe heterogeneous container.

推荐答案

忘记在OOP学校学到的东西!

3年后,我们走了一条路.我们现在有更好的语言. Swift,Rust,Kotlin,Go等.我们了解数据/值类型与操作它的代码之间的区别.适当的企业CLEAN架构在Java领域中倡导这一点.但是Java只是不为这种模式提供语言级别的支持.最终结果是大量使用(仍然很棒)RxJava之类的东西,以及执行代码生成的注释处理器等.但是,如今很难满足于Java的传统. Kotlin倾向于以Java不能以非常低廉的价格解决Java的问题:失去严格的源兼容性(不是Groovy).

Forget what you learned in OOP school!

We've come a ways in 3 years. We have much better languages now. Swift, Rust, Kotlin, Go, etc. We understand the difference between data/value types and the code that manipulates it. Proper enterprise CLEAN architecture advocates for this in the Java realm. But Java just doesn't provide language-level support for such patterns. The end result is heavy use of (still wonderful) things like RxJava, and annotation processors that do code generation, etc. But it's really hard to be happy dragging around Java's legacy these days. Kotlin tends to solve Java's problems in a way Java can't at a very very minimal price: loss of strict source compatibility (it's not Groovy).

原始答案,底部有更新.

如果类表示某些大型数据对象,则听起来大多数成员变量只是数据的容器.在这种情况下,严格遵循信息隐藏的OOP约定就不那么重要了.人们经常混淆该公约的目的,并最终滥用它.这仅仅是为了防止程序员不得不处理对象的复杂和不必要的内部工作.而不是遮罩整个对象,而只是遮罩不应弄乱的部分.如果您只是在映射数据库中的信息或充当存储容器,则代码如下:

If the class represents some large data object it sounds like most the member variables are just containers for data. When something like this is the case it's less important to strictly follow the OOP convention of information hiding. People often confuse the purpose of this convention and end up abusing it. It is merely to prevent programmers from having to deal with complicated and unnecessary inner workings of an object. Rather than mask an entire object, just mask the parts that shouldn't be messed with. In a case where you are simply mapping information from a database or are acting as a storage container, code like:

import java.util.Date;

public class Article {

    protected int id;

    protected String guid;
    protected String title;
    protected String link;
    protected Date pubDate;
    protected String category;
    protected String description;
    protected String body;
    protected String comments;
    
    protected Article (String articleTitle, String articleBody) {
        title = articleTitle;
        body = articleBody;
    }
    
    protected Article (String guid, String articleTitle, String articleLink,
            long publicationDate, String category, String description,
            String articleBody, String commentsLink) {
        this(articleTitle, articleBody);
        this.guid = guid;
        this.link = articleLink;
        this.pubDate = new Date(publicationDate);
        this.category = category;
        this.description = description;
        this.comments = commentsLink;
    }
    
    protected Article (int id, String guid, String articleTitle, String articleLink,
            long publicationDate, String category, String description,
            String articleBody, String commentsLink) {
        this(guid, articleTitle, articleLink, publicationDate, 
                category, description, articleBody, commentsLink);
        this.id = id;
    }

    protected int getId() {
        return id;
    }
    
    protected String getTitle() {
        return title;
    }

    protected String getGuid() {
        return guid;
    }

    protected String getLink() {
        return link;
    }

    protected String getComments() {
        return comments;
    }

    protected String getCategory() {
        return category;
    }

    protected String getDescription() {
        return description;
    }

    protected String getBody() {
        return body;
    }

    protected void setId(int id) {
        this.id = id;
    }

    protected void setGuid(String guid) {
        this.guid = guid;
    }

    protected void setTitle(String title) {
        this.title = title;
    }

    protected void setLink(String link) {
        this.link = link;
    }

    protected void setPubDate(Date pubDate) {
        this.pubDate = pubDate;
    }

    protected void setCategory(String category) {
        this.category = category;
    }

    protected void setDescription(String description) {
        this.description = description;
    }

    protected void setBody(String body) {
        this.body = body;
    }

    protected void setComments(String comments) {
        this.comments = comments;
    }

}

..是完全可恶的.

在这种情况下,实际上没有理由仅为了访问数据对象的成员而进行所有额外的工作.特别是如果您只是在几行外部代码中使用它们:

In a case like this there is really no reason to go through all the extra work just to access members of the data object. Especially if you're just using them in a few external lines of code:

public OtherClass {

    private Article article;

    public OtherClass(Article data) {
        article = data;
    }
    
    public String getArticleContents() {

        return (new StringBuilder())
        .append(article.getTitle())
        .append(article.getCategory())
        .append(dateToString(article.getPubDate())
        .append(article.getBody())
        .toString();
    }
}

只需直接访问成员并保存数百行代码即可(就像您建议的那样).

Just access the members directly and save yourself hundreds of lines of code (like you've suggested you were trying to do).

这是该问题的第二个答案.

This leads to the second answer to this question..

您的代码可能正在烂掉.大厨拉姆西(Ramsey)将感到ham愧.原因如下:

Your code may be rotting. Chef Ramsey would be ashamed. Here's why:

很显然,上面的OtherClass完全没有用,因为它的功能可以(并且应该)放在Article类中,而不是包含在其他一些无用的,不需要文件系统的OtherClass中.如果这样做,您甚至可能甚至都不需要使用getter和setter OtherClass,因为与之交互的内容可能只需要文章内容,而不需要标题,正文,等.在这种方法中,Article类隐藏了外界的所有内容,仅提供了绝对必要的信息.

Clearly the above OtherClass is entirely useless because its functionality could (and should) be placed in the Article class instead of contained in some other, useless, unwanted , file-system-littering OtherClass. If you do that you can forget about even needing the getters and setters and OtherClass because things interfacing with it may only need the article contents rather than the title, body, etc., separately. In this approach the Article class hides everything from the outside world and only provides the information that's absolutely needed.

由于这是对您的问题的两个完全可行的答案,因此必须有一个解决方案 j 上.

Since these are two perfectly viable answers to your problem, there must be a solut j on.

尽管您可以使用闭包为对象建模,还有更好的方法.有趣的是,在将功能视为一等公民的语言中,您可以完全使用地图为传统的面向对象范例建模-正如您在重构30多名成员字段类系统中所发现的那样,被给予.

Although you can use closures to model objects, there are even better approaches. The interesting thing is that in a language that treats functions as first class citizens, you can model the traditional object oriented paradigm entirely using maps -- as you began to figure out in your refactoring of the 30-plus-member-field class system you were given.

将此与原始的Article + OtherClass方法进行比较:

Compare this to the original Article + OtherClass approach:

(defn Article []
  (let [id (atom nil)
        guid  (atom nil)
        title  (atom nil)
        link (atom nil)
        pubdate (atom nil)
        category (atom nil)
        description (atom nil)
        body (atom nil)
        comments (atom nil)

        set (fn [g t l p cg d b cm]
              (do (reset! guid g)
                  (reset! title t)
                  (reset! link l)
                  (reset! pubdate p)
                  (reset! category cg)
                  (reset! description d)
                  (reset! body b)
                  (reset! commments cm)))
        get (fn [] [@guid
                    @link
                    @description
                    @comments
                    :content (content)])

        content #(str title category pubdate body)]
    {:get get, :set set}))

上面的说明是一个系统,该系统从两个答案中获取分数并将它们组合成一个隐藏不需要的成员,合并逻辑功能(获取内容"的功能)并使用不需要大量内容的语言的系统闪亮的样板代码..

This above examble is a system that takes points from both answers and combines them into one that hides unneeded members, incorporates logical functionality (the function to get the 'content'), and uses a language that doesn't need incredible amounts of shiny boilerplate code..

虽然这是一个很好的示例,说明了如何使用函数式语言对对象进行建模,但对于Clojure和函数式编程而言,这并不是完全惯用的.有关处理结构化数据的更简便方法,请参见Clojure StructMaps 记录.

While this is a nice example of how to model an object in a functional language, it's not entirely idiomatic to Clojure and functional programming in general. For even easier ways to do structured data, look at Clojure StructMaps and Records.

只需使用科林.它正在全面治愈Java病.它对所有这些东西都具有一流的语言支持,并且编译器甚至可以帮助您摆脱无用的样板.它已经存在7年了,自2016年2月以来一直稳定发布.如果我本来就知道的话,我可能就不会包括Closure.

Just use Kotlin. It's curing Java sickness across the board. It has first class language support for all these things and the compiler will even help you get rid of useless boilerplate. It's been around for over 7 years and at a stable release for since February 2016. If I'd have known about it originally, I probably would not have included Closure.

这篇关于如何避免在具有许多实例变量的类中使用getter/setter方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆