需要重构帮助 - 具有许多参数的方法

时间:2013-08-01 08:25:22

标签: java methods parameters refactoring

我找到了一些精彩的代码,其中有超过30个参数给了一个方法(我丢失了计数)。 该实现包含超过500行 if / then / else switch 块。

怎么能以干净的方式重构? 你对此有何建议?

许多实现都在整个应用程序中,并且都会推送这些参数。

问题方法:

public static User findUser (
    String userCode, String lastName, String firstName,
    String alternativeLastName, String sex, int day, int month, int year,
    String locationParameter, String locationInfo,
    Id groupId, Id organizationId, Id orderId,
    Id orderGroupId, Id orderOrganizationId,
    List<Id> groupIds, List<Id> organizationIds,
    List<Id> orderIds, List<Id> orderGroupIds,
    List<Id> orderOrganizationIds,
    String timeRange, Long daysAgo,
    Date dateFrom, Date dateUntil,
    CodingMap codingMap, List<String> languageList, String visitType,
    Account account, List<Group> accountGroups,
    Organization accountOrganization, Profile profile,
    AccessProfile accessProfile, Locale locale, int maxResults,
    long newTimeRange, long minimumTimeRange,
    boolean hideUserWithoutName, boolean withOrderInfo, boolean withVisitInfo,
    boolean orderEntryAvailable, 
    boolean hideDiscontinuedResults, boolean checkPatientAccessRights, boolean searchForCopies,
    boolean inOrderEntry, boolean allPatientCodes,
    boolean addPatientCharacteristics, boolean showSpeciesColumn,
    boolean patientDefinedWithPrivacyLevel,
    boolean excludePatientsWithoutCodeInSelectedCodingSystem
    ) throws CompanyException {
...
}

在这样的地方使用:

User.findUser(member.getCode(), context.lastName, context.firstName,
    context.alternativeLastName, context.sex,
    context.birthDate.getDay(), context.birthDate.getMonth(), 
    context.birthDate.getYear(),
    context.locationParameter, context.locationInfo,
    null, null, null, null, null, null, null, null, null, null,
    session.timeRange(), session.daysAgo(), session.dateFrom(),
    session.dateUntil(),
    order.codingMap(), order.languageList(), member.visitType(),
    null, null, null, null, null, locale, 25, 1000L,200L,
    session.point.booleanValue(), session.infobooleanValue(), false,
    true, true, true, true, true, true, true, false, false, false);

7 个答案:

答案 0 :(得分:3)

首先,我会进一步检查它的实际用法。

如果您发现使用这些参数的特定子集重复调用它,您可能会执行以下操作:

User.FindUserByABC(A, B, C)
User.FindUserByXYZ(X, Y, Z)

如果这不合适,正如其他人建议的那样我会创建一个SearchParams类。所有属性都将初始化为默认值,其用法仅涉及设置每次调用所需的相关搜索项,例如:

SearchParams params = new SearchParams(){A = "...", Z = "..."}
User.FindUser(params)

后者肯定会至少清理调用代码,对现有的底层机制影响最小。

答案 1 :(得分:2)

您可以创建Predicate界面(或使用guava's):

interface Predicate<T> { boolean apply(T input); }

并将您的方法更改为:

User findUser(Predicate<User> p) {
    //find user
}

然后你这样称呼它:

findUser(new Predicate<User> () {
    public boolean apply(User user) {
        return member.getCode().equals(user.getCode())
               && user.lastname.equals(context.lastname); //etc.
    }
});

下一步可能是使用流畅的语法引入UserQuery以轻松创建这些查询。它看起来像是:

UserQuery query = new UserQuery();
query.lastname(context.lastname)
       .code(member.getCode()); //etc
Predicate<User> p = query.build();
User user = findUser(p);

答案 2 :(得分:2)

使用构建器为所有必要参数创建方法类

public class FindUser {
    // fields that represent necessary parameters
    private final String lastName;
    ...

    // fields that represent optional parameters
    private Id groupId;
    ...

    // builder class for necessary parameters
    public static class Builder {
        private String lastName;
        ...

        public Builder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }
        ...

        public FindUser build {
            return new FindUser(this);
        }
    }

    // constructor taking a builder with all necessary parameters
    private FindUser(Builder builder){
        // check here, whether all fields are really set in the builder
        this.lastName = builder.lastName;
        ...
    }

    // setters for all optional parameters
    public FindUser groupId(Id groupId) {
        this.groupId = groupId;
        return this;
    }
    ...

    // the action
    public User compute() {
        ...
    }
}

将前一个方法体复制到方法类的新计算方法中。之后,您可以通过将许多代码块提取到自己的方法和类中来重构此方法。

最后两步:

1)在旧的findUser方法中,创建方法类的新对象并调用compute方法。这不会减小参数列表的大小。

2)更改find​​User方法的所有用法以使用新方法类,然后删除旧的findUser方法。

用法将是:

FindUser fu = new FindUser.Builder()
        .lastname("last name")
        ...
        .build()
        .groupId(new GroupId())
        ...;
User user = fu.compute();

答案 3 :(得分:1)

将所有这些参数封装在类

class MethodParam {
        String userCode; String lastName; String firstName;
        String alternativeLastName; String sex; int day; int month; int year;
        String locationParameter; String locationInfo;
        Id groupId; Id organizationId; Id orderId;
        Id orderGroupId; Id orderOrganizationId;
        List<Id> groupIds; List<Id> organizationIds;
        List<Id> orderIds; List<Id> orderGroupIds;
        List<Id> orderOrganizationIds;
        String timeRange; Long daysAgo;
        Date dateFrom; Date dateUntil;
        CodingMap codingMap; List<String> languageList; String visitType;
        Account account; List<Group> accountGroups;
        Organization accountOrganization; Profile profile;
        AccessProfile accessProfile; Locale locale; int maxResults;
        long newTimeRange; long minimumTimeRange;
        boolean hideUserWithoutName; boolean withOrderInfo; boolean withVisitInfo;
        boolean orderEntryAvailable; 
        boolean hideDiscontinuedResults; boolean checkPatientAccessRights; boolean searchForCopies;
        boolean inOrderEntry; boolean allPatientCodes;
        boolean addPatientCharacteristics; boolean showSpeciesColumn;
        boolean patientDefinedWithPrivacyLevel;
        boolean excludePatientsWithoutCodeInSelectedCodingSystem;
}

然后将该类发送到方法

findUser(MethodParam param) {
  ....
}

OR

将所有参数放在地图中,并使用明确定义的常量作为键

Map<String,Object> paramMap = new HashMap<>();
paramMap.put(Constants.USER_CODE, userCode);

...

并接受地图作为参数

findUser(Map<String, Object> param) {
      ....
}

就if / else,switch而言,它们必须分为逻辑小子方法以降低周期复杂度

答案 4 :(得分:1)

您最好使用所有这些参数创建一个对象作为属性,并在整个代码中处理它。

    public class MyObeject{
    String userCode, String lastName, String firstName,
    String alternativeLastName, String sex, int day, int month, int year,
    String locationParameter, String locationInfo,
    Id groupId, Id organizationId, Id orderId,
    Id orderGroupId, Id orderOrganizationId,
    List<Id> groupIds, List<Id> organizationIds,
    List<Id> orderIds, List<Id> orderGroupIds,
    List<Id> orderOrganizationIds,
    String timeRange, Long daysAgo,
    Date dateFrom, Date dateUntil,
    CodingMap codingMap, List<String> languageList, String visitType,
    Account account, List<Group> accountGroups,
    Organization accountOrganization, Profile profile,
    AccessProfile accessProfile, Locale locale, int maxResults,
    long newTimeRange, long minimumTimeRange,
    boolean hideUserWithoutName, boolean withOrderInfo, boolean withVisitInfo,
    boolean orderEntryAvailable, 
    boolean hideDiscontinuedResults, boolean checkPatientAccessRights, boolean searchForCopies,
    boolean inOrderEntry, boolean allPatientCodes,
    boolean addPatientCharacteristics, boolean showSpeciesColumn,
    boolean patientDefinedWithPrivacyLevel,
    boolean excludePatientsWithoutCodeInSelectedCodingSystem
}

findUser(MyObject obj);

并且仅填写每种情况下所需的属性。我想其中一些在某些情况下是可选的。

答案 5 :(得分:0)

我用一个小的sql数据库替换它,每个用户都有一个简单的主键。

答案 6 :(得分:0)

使用自我解释的制定者制作建筑师?我知道30个setter并不是更好,但你至少可以以编程方式生成它们。它真的有很多必要的参数吗?