需要重构帮助-方法的参数太多



我发现了一些出色的代码,其中为一个方法提供了30多个参数(我记不清了)。该实现包含500多行带有if/then/else开关块。

如何以一种干净的方式重构它?你对此有什么建议?

应用程序中有很多实现,并且都推送这些参数。

问题方法:

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);

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

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

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

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

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

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

您可以创建一个Predicate接口(或使用番石榴):

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);

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

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) 将findUser方法的所有用法更改为使用新的方法类,然后删除旧的findUser方法。

用途为:

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

将所有这些参数封装在类中

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) {
  ....
}

将所有参数放在映射中,并将定义良好的常量作为密钥

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

并接受地图作为参数

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

就if/else,switch而言,它们必须被划分为逻辑小个子方法,以降低循环复杂度

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

    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);

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

我会用一个小的sql数据库来代替它,为每个用户提供一个简单的主键。

用自我解释的setter创建生成器?我知道30个setter并没有好到哪里去,但您至少可以通过编程生成它们。真的有那么多必要的参数吗?

最新更新