Adapting the Builder pattern for method invocation

一笑奈何 提交于 2019-12-09 12:44:59

问题


This is an attempt to understand a portion of ITEM 40: Design Method Signatures Carefully from Effective Java 2nd Edition.

One of the things suggested to improve method signature readability is to aim for four or fewer parameters. It is suggested that longer parameter lists be managed by using a variety of techniques one of which is as follows :

A third technique that combines aspects of the first two is to adapt the Builder pattern (Item 2) from object construction to method invocation. If you have a method with many parameters, especially if some of them are optional, it can be beneficial to define an object that represents all of the parameters, and to allow the client to make multiple “setter” calls on this object, each of which sets a single parameter or a small, related group. Once the desired parameters have been set, the client invokes the object’s “execute” method, which does any final validity checks on the parameters and performs the actual computation.

I am familiar with the Builder pattern as it is used for object construction, but am not sure whether I have correctly understood how to adapt it to method invocation.

Here is what I have thus far :
( I have attempted to improve the method invocation for the move method)

public class Space {

    public static class Builder {
        // Required parameters
        private final int x;
        private final int y;
        private final int z;

        // optional params
        private long time = 0;

        public Builder(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Builder time(long val) {
            time = val;
            return this;
        }

        public void move() {
            if (x == 0 || y == 0 || z == 0) {
                throw new IllegalArgumentException("Cannot move to the centre of the universe");
            }

            // Do the actual work here
        }
    }

//  public void move(int x, int y, int z, long time) {
//      // Do the work here
//  }

    public static void main(String[] args) {
        new Builder(1, 1, -1).time(1234).move();
    }
}

Is my interpretation of Joshua Bloch's advice correct ?


回答1:


I've done reifications of the Builder pattern using an interface defined inside the class I want to instantiate.

See here for extra info on this and the related "bidirectional builder": http://www.javaworld.com/javaworld/jw-01-2004/jw-0102-toolbox.html?page=3

I would do something like the following; although this is overkill perhaps for 3 required fields, it is very helpful for constructing larger objects while accounting for every required field.

public Class Space
{    
    public interface Builder
    {
        public Space build();
        public int x();
        public int y();
        public int z();
    }

    // Build a complete Space object accounting for every field. 
    public Space(Space.Builder spaceBuilder)
    {
        this.x = spaceBuilder.x();
        this.y = spaceBuilder.y();
        this.z = spaceBuilder.z();
        this.time = builder.time();
    }

    // Might not be necessar if you update time when you update x, y, and z
     public Builder time(long val)
     {
            time = val;
            return this;
     }

     public void move()
     {
        // ...
     }

    public static void main(String[] args)
    {
        new Builder()
        {   
            @Override
            public Space build(){ return new Space(this);}

            @Override
            public int x(){ return 1;}

            @Override
            public int y{ return 1;}

            @Override int z{ return -1;}
        }.build().time(1234).move();
    }
}



回答2:


The way that I see it, you'd have your builder class associated with the parent class (like with a non-static inner class) and clients would use it like this:

Space space = new Space();
...
Space.MoveBuilder moveBuilder = space.new MoveBuilder(1, 1, -1);
moveBuilder.setTime(1234);
moveBuilder.execute();

It could be further simplified by using a fluent builder and a factory method:

space.startMove(1, 1, -1).withTime(1234).endMove();



回答3:


I created Java Method Invocation Builder that, at compile time, generates builders that invokes methods. It also adds support for default values on method parameters in Java.

It can be used on classes or interfaces. By adding @GenerateMethodInvocationBuilder and @Default annotations to the type.

@GenerateMethodInvocationBuilder
public interface BitBucketServerService {
 @GET("/rest/api/1.0/projects/{projectkey}/repos/{repositoryslug}/pull-requests?direction={direction}&at={at}&state={state}&order={order}&withattributes={withattributes}&withproperties={withproperties}")
 Call<BitbucketServerResponse<BitBucketServerPullRequest>> pullRequests(//
   @Default("PROJ") @Query("projectkey") String projectKey,//
   @Default("REPO") @Query("repositoryslug") String repositoryslug,//
   @Default("INCOMING") @Query("direction") String direction,//
   @Default("23") @Query("at") String at,//
   @Default("OPEN") @Query("state") String state,//
   @Default("NEWEST") @Query("order") String order,//
   @Default("true") @Query("withattributes") String withattributes,//
   @Default("true") @Query("withproperties") String withproperties);
}

Then, a builder will be generated when code is compiled and you can invoke it with default parameters:

BitBucketServerServicePullRequestsBuilder.pullRequests()
 .invoke(bitBucketServerService);

Or set any parameters you like:

BitBucketServerServicePullRequestsBuilder.pullRequests()
 .withAt("24")
 .invoke(bitBucketServerService);

More on GitHub: https://github.com/tomasbjerre/java-method-invocation-builder



来源:https://stackoverflow.com/questions/13954672/adapting-the-builder-pattern-for-method-invocation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!