简体   繁体   中英

Accepting different types of arguments in Java

This is a question about something that I am not sure how to solve in Java. I want to make triple statements based on three types of data, URI, String or Literal, each type is encoded differently. I have written encode methods that accept these types.

public static String makeStatement(URI subject, URI predicate, String object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

public static String makeStatement(String subject, URI predicate, String object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

public static String makeStatement(URI subject, URI predicate, Literal object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

private static String encode(String binding) {
    return "?" + binding;
}

private static String encode(URI uri) {
    return "<" + uri.stringValue() + ">";
}

private static String encode(Literal literal) {
    return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
}

But as I can accept any combination of these types this would require 9 makeStatement functions, which are basically doing the same thing and that seems like a bad idea, especially since it might be possible I want to add another type later on.

Normally I would answer such a question with the suggestion to create a superClass, but I cannot edit String, URI and Literal. Another option would be to define

public static String makeStatement(Object subject, Object predicate, Object object) {
    String encodedSubject = "", encodedPredicate = "", encodedObject = "";
    if (subject.getClass().equals(URI.class)) {
        encodedSubject = encode((URI) subject);
}
    return " " + encode(encodedSubject) + " " + encode(encodedPredicate) + " " + encode(encodedObject) + ".\n";
}

and then check the classes for each argument, but I consider this not very elegant. Another suggestion would be to define something like makeStatement(URI subjectURI, String subjectString, Literal subjectLiteral, URI predicateURI.. etc) and then check which arguments are null and go from there, but that would mean typing a lot of nulls when I call the function. A third option would be https://stackoverflow.com/a/12436592/1014666 , but again this require quite some extra typing when calling the makeStatement function.

Any suggestions?

You can use a builder pattern:

    public class StatementMaker {
    private static String encode(String binding) {
        return "?" + binding;
    }

    private static String encode(URI uri) {
        return "<" + uri.stringValue() + ">";
    }

    private static String encode(Literal literal) {
        return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
    }

    public static Statement from(String b) {
        return new Statement(encode(b));
    }

    public static Statement from(URI b) {
        return new Statement(encode(b));
    }

    public static Statement from(Literal b) {
        return new Statement(encode(b));
    }

    public static class Statement {

        private StringBuilder buf;
        private Statement(String s) {
            buf = new StringBuilder(" ");
            buf.append(s);
        }

        public Statement with(String s) {
            buf.append(" ").append(encode(b));
            return this;
        }

        public Statement with(URI s) {
            buf.append(" ").append(encode(b));
            return this;
        }

        public Statement with(Literal s) {
            buf.append(" ").append(encode(b));
            return this;
        }

        public String toString() {
            return buf.toString() + ".\n";
        }

    }
}

You can now construct statement as such:

StatementMaker.from(subject).with(predicate).with(object).toString()

In code that needs the statements you can shorten the code further with static import:

import static my.package.StatementMaker.from;

Then the statement is reduced to:

from(subject).with(predicate).with(object).toString()

You can add 3 more methods to inner class:

public static class Statement {

    private StringBuilder buf;
    private Statement(String s) {
        buf = new StringBuilder(" ");
        buf.append(s);
    }

    public Statement with(String s) {
        buf.append(" ").append(encode(b));
        return this;
    }

    public Statement with(URI s) {
        buf.append(" ").append(encode(b));
        return this;
    }

    public Statement with(Literal s) {
        buf.append(" ").append(encode(b));
        return this;
    }

    public String and(String s) {
        buf.append(" ").append(encode(b));
        return buf.toString() + ".\n";
    }

    public String and(URI s) {
        buf.append(" ").append(encode(b));
        return buf.toString() + ".\n";
    }

    public String and(Literal s) {
        buf.append(" ").append(encode(b));
        return buf.toString() + ".\n";
    }


    public String toString() {
        return buf.toString() + ".\n";
    }

}

Then you can use avoid a toString() call like this:

String statement = from(subject).with(predicate).and(object);

Method overloading works great if there's only a few options. What you have here is a bit obsessive. You don't need to have all the options if there's an easy way to convert from one to another.

So forget about having every possible choice and make the most used ones available.

Normally I would answer such a question with the suggestion to create a superClass, but I cannot edit String, URI and Literal. Another option would be to define

I'd go for a similar approach, but instead of extracting a superclass, which as you stated, you cannot do, you could create a wrapper.

public class LiteralWrapper {
    private String string = null;
    private URI uri = null;
    private Literal literal = null;

    public LiteralWrapper(String sting) {
        this.string = string;
    }

    public LiteralWrapper(URI uri) {
        this.uri = uri;
    }

    public LiteralWrapper(Literal literal) {
        this.literal = literal;
    }

    // Note that this class is immutable, 
    // so you know you cannot have more than one non-null member. 
    // Probably not a bad idea to add some getters, though.


    /* The encode functions from your original question */

    private static String encode(String binding) {
        return "?" + binding;
    }

    private static String encode(URI uri) {
        return "<" + uri.stringValue() + ">";
    }

    private static String encode(Literal literal) {
        return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
    }

    @Override
    public String toString() {
        if (literal != literal) {
            return encode(literal);
        }
        if (uri != null) {
            return encode(uri);
        }
        return encode(string);
    }
}

Now your makeStatement code becomes trivial:

public static String makeStatement(LiteralWrapper subject, LiteralWrapper predicate, LiteralWrapper object) {
    return " " + subject + " " + predicate + " " + object + ".\n";
}

EDIT:
As per the comment below, this makes calling makeStatement a bit annoying. Instead of being able to do makeStatement(myString, myUri, myLiteral) , you're force to call makeStatement(new LiteralWrapper(myString), new LiteralWrapper(myUri), new LiteralWrapper(myLiteral)) . This problem can't be completely avoided with the given solution, but it can be mitigated by introducing some syntactic sugar in the form of factory methods:

public static LiteralWrapper wrap(String string) {
    return new LiteralWrapper(string);
}

public static LiteralWrapper wrap(URI uri) {
    return new LiteralWrapper(uri);
}

public static LiteralWrapper wrap(Literal literal) {
    return new LiteralWrapper(literal);
}

Now, you can staticly import these wrappers wherever you need to use makeStatement :

import static org.some.package.LiteralWrapper.wrap;
/* other imports*/

public class MyClass {
    public void someFunction() {
        /* some business logic */

        makeStatemet(wrap(myString), wrap(myURI), wrap(myLiteral));
    }
}
public static String makeStatement(Object subject, Object predicate, Object object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

private static String encode(Object obj) {
   String  encodedOj ="";
   if (obj.getClass().equals(URI.class)) {
        encodedOj = encode((URI) obj);
   }else if(obj.getClass().equals(Literal.class)){
      encodedOj = encode((Literal) obj);
   }else if(obj.getClass().equals(String.class)){
      encodedOj = encode((String) obj);
   } 
   return encodedOj;
}

private static String encode(String binding) {
    return "?" + binding;
}

private static String encode(URI uri) {
    return "<" + uri.stringValue() + ">";
}

private static String encode(Literal literal) {
    return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
}

You can create fun and easy to use wrapper using static factory methods (see Effective Java, Item 1 ) and anonymous classes (acting as something like closures).

Here is how:

public class Item {

    private static interface Methods {
        public String encode();
    }

    private final Methods methods;

    private Item(Methods methods) {
        this.methods = methods;
    }

    public static Item of(final String binding) {
        return new Item(new Methods() {
            @Override
            public String encode() {
                return "?" + binding;
            }
        });
    }

    public static Item of(final URI uri) {
        return new Item(new Methods() {
            @Override
            public String encode() {
                return "<" + uri.stringValue() + ">";
            }
        });
    }

    public static Item of(final Literal literal) {
        return new Item(new Methods() {
            @Override
            public String encode() {
                return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
            }
        });
    }

    public String encode() {
        return methods.encode();
    }
}

It's really easy to add new supported types (which was one of your requirements) with this approach: just create new static factory method accepting this type: Item.of(NewType val) .

So you would have a method:

public static String makeStatement(Item subject, Item predicate, Item object) {
    return subject.encode() + " " + predicate.encode() + " " + object.encode();
}

And call it like that:

makeStatement(Item.of(subject), Item.of(predicate), Item.of(object));

Adding new methods is also quite easy (but it would be sort of like modification instead of extension) -- just add them to the Methods interface and implement in closures for all supported types. The compiler will make you to, anyway.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM