diff --git a/src/main/java/de/prob2/jupyter/CommandUtils.java b/src/main/java/de/prob2/jupyter/CommandUtils.java
index 35713d5e2441416d480e1798ff8788ac838d2629..4fe551c0bd3210533bc4be78fc1a3638bd5709a8 100644
--- a/src/main/java/de/prob2/jupyter/CommandUtils.java
+++ b/src/main/java/de/prob2/jupyter/CommandUtils.java
@@ -108,7 +108,7 @@ public final class CommandUtils {
 				break;
 			}
 			
-			final Parameter.SplitResult splitSingleArg = param.split(remainingArgs);
+			final Parameter.SplitResult splitSingleArg = param.getSplitter().split(remainingArgs);
 			splitArgs.add(param, splitSingleArg.getSplitArg());
 			remainingArgs = splitSingleArg.getRemainingArgString();
 			
@@ -121,7 +121,7 @@ public final class CommandUtils {
 	}
 	
 	private static <T> void validateSplitParameter(final @NotNull ParsedArguments parsed, final @NotNull SplitArguments splitArgs, final @NotNull Parameter<T> param) {
-		parsed.put(param, param.validate(splitArgs.get(param)));
+		parsed.put(param, param.getValidator().validate(param, splitArgs.get(param)));
 	}
 	
 	public static @NotNull ParsedArguments validateSplitArgs(final @NotNull Parameters parameters, final SplitResult split) {
diff --git a/src/main/java/de/prob2/jupyter/Parameter.java b/src/main/java/de/prob2/jupyter/Parameter.java
index f609eb13a7f6e9dfaca9bc096e55e1dafe841992..ae7c214e30feb99d7b77d6af622de0b13e0ef02a 100644
--- a/src/main/java/de/prob2/jupyter/Parameter.java
+++ b/src/main/java/de/prob2/jupyter/Parameter.java
@@ -1,6 +1,7 @@
 package de.prob2.jupyter;
 
 import java.util.List;
+import java.util.Optional;
 
 import com.google.common.base.MoreObjects;
 
@@ -27,28 +28,82 @@ public abstract class Parameter<T> {
 		}
 	}
 	
+	public interface Splitter {
+		public static final @NotNull Parameter.Splitter REGULAR = argString -> {
+			final String[] split = CommandUtils.ARG_SPLIT_PATTERN.split(argString, 2);
+			return new SplitResult(split[0], split.length > 1 ? split[1] : "");
+		};
+		public static final @NotNull Parameter.Splitter REMAINDER = argString -> new SplitResult(argString, "");
+		
+		public abstract Parameter.SplitResult split(final @NotNull String argString);
+	}
+	
+	public interface Validator<T> {
+		public static final @NotNull Parameter.Validator<@NotNull String> EXACTLY_ONE = (param, argValues) -> {
+			if (argValues.isEmpty()) {
+				throw new UserErrorException("Missing required parameter " + param.getIdentifier());
+			} else if (argValues.size() > 1) {
+				throw new UserErrorException("Non-repeating parameter " + param.getIdentifier() + " cannot appear more than once");
+			}
+			
+			return argValues.get(0);
+		};
+		public static final @NotNull Parameter.Validator<@NotNull Optional<String>> ZERO_OR_ONE = (param, argValues) -> {
+			if (argValues.size() > 1) {
+				throw new UserErrorException("Non-repeating parameter " + param.getIdentifier() + " cannot appear more than once");
+			}
+			
+			return argValues.stream().findAny();
+		};
+		public static final @NotNull Parameter.Validator<@NotNull List<@NotNull String>> ONE_OR_MORE = (param, argValues) -> {
+			if (argValues.isEmpty()) {
+				throw new UserErrorException("Missing required parameter " + param.getIdentifier());
+			}
+			
+			return argValues;
+		};
+		public static final @NotNull Parameter.Validator<@NotNull List<@NotNull String>> ZERO_OR_MORE = (param, argValues) -> argValues;
+		
+		public abstract T validate(final @NotNull Parameter<T> param, final @NotNull List<@NotNull String> argValues);
+	}
+	
 	private final @NotNull String identifier;
+	private final boolean repeating;
+	private final @NotNull Parameter.Splitter splitter;
+	private final @NotNull Parameter.Validator<T> validator;
 	
-	protected Parameter(final @NotNull String identifier) {
+	protected Parameter(final @NotNull String identifier, final boolean repeating, final @NotNull Parameter.Splitter splitter, final @NotNull Parameter.Validator<T> validator) {
 		super();
 		
 		this.identifier = identifier;
+		this.repeating = repeating;
+		this.splitter = splitter;
+		this.validator = validator;
 	}
 	
 	public @NotNull String getIdentifier() {
 		return this.identifier;
 	}
 	
-	public abstract boolean isRepeating();
+	public boolean isRepeating() {
+		return this.repeating;
+	}
 	
-	public abstract Parameter.SplitResult split(final @NotNull String argString);
+	public @NotNull Parameter.Splitter getSplitter() {
+		return this.splitter;
+	}
 	
-	public abstract T validate(final @NotNull List<@NotNull String> argValues);
+	public @NotNull Parameter.Validator<T> getValidator() {
+		return this.validator;
+	}
 	
 	@Override
 	public String toString() {
 		return MoreObjects.toStringHelper(this)
 			.add("identifier", this.getIdentifier())
+			.add("repeating", this.isRepeating())
+			.add("splitter", this.getSplitter())
+			.add("validator", this.getValidator())
 			.toString();
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/PositionalParameter.java b/src/main/java/de/prob2/jupyter/PositionalParameter.java
index f3d448e6b19f4484879437e63c08d1db4dc4f8c9..f6f927cb68b269e8f259f2cad10280d19eabbe72 100644
--- a/src/main/java/de/prob2/jupyter/PositionalParameter.java
+++ b/src/main/java/de/prob2/jupyter/PositionalParameter.java
@@ -5,146 +5,44 @@ import java.util.Optional;
 
 import org.jetbrains.annotations.NotNull;
 
-public abstract class PositionalParameter<T> extends Parameter<T> {
-	public abstract static class ExactlyOne extends PositionalParameter<@NotNull String> {
-		protected ExactlyOne(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public boolean isRepeating() {
-			return false;
-		}
-		
-		@Override
-		public @NotNull String validate(final @NotNull List<@NotNull String> argValues) {
-			if (argValues.isEmpty()) {
-				throw new UserErrorException("Missing required parameter " + this.getIdentifier());
-			} else if (argValues.size() > 1) {
-				throw new AssertionError("Regular (single) required parameter " + this.getIdentifier() + " has more than one value, this should never happen!");
-			}
-			
-			return argValues.get(0);
-		}
-	}
-	
-	public abstract static class ZeroOrOne extends PositionalParameter<@NotNull Optional<String>> {
-		protected ZeroOrOne(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public boolean isRepeating() {
-			return false;
-		}
-		
-		@Override
-		public @NotNull Optional<String> validate(final @NotNull List<@NotNull String> argValues) {
-			if (argValues.size() > 1) {
-				throw new AssertionError("Regular (single) optional parameter " + this.getIdentifier() + " has more than one value, this should never happen!");
-			}
-			
-			return argValues.stream().findAny();
-		}
-	}
-	
-	public static final class RequiredSingle extends PositionalParameter.ExactlyOne {
+public class PositionalParameter<T> extends Parameter<T> {
+	public static final class RequiredSingle extends PositionalParameter<@NotNull String> {
 		public RequiredSingle(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public @NotNull Parameter.SplitResult split(final @NotNull String argString) {
-			return splitOnce(argString);
+			super(identifier, false, Parameter.Splitter.REGULAR, Parameter.Validator.EXACTLY_ONE);
 		}
 	}
 	
-	public static final class OptionalSingle extends PositionalParameter.ZeroOrOne {
+	public static final class OptionalSingle extends PositionalParameter<@NotNull Optional<String>> {
 		public OptionalSingle(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public @NotNull Parameter.SplitResult split(final @NotNull String argString) {
-			return splitOnce(argString);
+			super(identifier, false, Parameter.Splitter.REGULAR, Parameter.Validator.ZERO_OR_ONE);
 		}
 	}
 	
 	public static final class RequiredMultiple extends PositionalParameter<@NotNull List<@NotNull String>> {
 		public RequiredMultiple(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public boolean isRepeating() {
-			return true;
-		}
-		
-		@Override
-		public @NotNull Parameter.SplitResult split(final @NotNull String argString) {
-			return splitOnce(argString);
-		}
-		
-		@Override
-		public @NotNull List<@NotNull String> validate(final @NotNull List<@NotNull String> argValues) {
-			if (argValues.isEmpty()) {
-				throw new UserErrorException("Missing required parameter " + this.getIdentifier());
-			}
-			
-			return argValues;
+			super(identifier, true, Parameter.Splitter.REGULAR, Parameter.Validator.ONE_OR_MORE);
 		}
 	}
 	
 	public static final class OptionalMultiple extends PositionalParameter<@NotNull List<@NotNull String>> {
 		public OptionalMultiple(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public boolean isRepeating() {
-			return true;
-		}
-		
-		@Override
-		public @NotNull Parameter.SplitResult split(final @NotNull String argString) {
-			return splitOnce(argString);
-		}
-		
-		@Override
-		public @NotNull List<@NotNull String> validate(final @NotNull List<@NotNull String> argValues) {
-			return argValues;
+			super(identifier, true, Parameter.Splitter.REGULAR, Parameter.Validator.ZERO_OR_MORE);
 		}
 	}
 	
-	public static final class RequiredRemainder extends PositionalParameter.ExactlyOne {
+	public static final class RequiredRemainder extends PositionalParameter<@NotNull String> {
 		public RequiredRemainder(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public @NotNull Parameter.SplitResult split(final @NotNull String argString) {
-			return new Parameter.SplitResult(argString, "");
+			super(identifier, false, Parameter.Splitter.REMAINDER, Parameter.Validator.EXACTLY_ONE);
 		}
 	}
 	
-	public static final class OptionalRemainder extends PositionalParameter.ZeroOrOne {
+	public static final class OptionalRemainder extends PositionalParameter<@NotNull Optional<String>> {
 		public OptionalRemainder(final @NotNull String identifier) {
-			super(identifier);
-		}
-		
-		@Override
-		public @NotNull Parameter.SplitResult split(final @NotNull String argString) {
-			return new Parameter.SplitResult(argString, "");
+			super(identifier, false, Parameter.Splitter.REMAINDER, Parameter.Validator.ZERO_OR_ONE);
 		}
 	}
 	
-	protected PositionalParameter(final @NotNull String identifier) {
-		super(identifier);
-	}
-	
-	@NotNull
-	static Parameter.SplitResult splitOnce(final @NotNull String argString) {
-		final String[] split = CommandUtils.ARG_SPLIT_PATTERN.split(argString, 2);
-		return new Parameter.SplitResult(split[0], split.length > 1 ? split[1] : "");
+	public PositionalParameter(final @NotNull String identifier, final boolean repeating, final @NotNull Parameter.Splitter splitter, final @NotNull Parameter.Validator<T> validator) {
+		super(identifier, repeating, splitter, validator);
 	}
 }