JavaScript 带验证的链式构建器

JavaScript chained builder with validation

在此 Java class 中,请注意如何禁止使用构造函数并将其替换为指导实例化和进行验证的接口驱动构建器

public class Position implements Serializable {

    private BigDecimal capital;
    private BigDecimal tolerableRiskInPercentOfCapitalPerTrade;
    private Direction direction;
    private BigDecimal pricePerUnit;
    private BigDecimal stopLossPricePerUnit;

    private Position(){}

    public final BigDecimal getTotalTolerableRiskPerTrade() {
        return capital.multiply(tolerableRiskInPercentOfCapitalPerTrade.divide(new BigDecimal(100)));
    }

    public final BigDecimal getStopLossPerUnitLoss() {
        if (direction.equals(Direction.LONG)){
            return pricePerUnit.subtract(stopLossPricePerUnit);
        } else {
            return stopLossPricePerUnit.subtract(pricePerUnit);
        }
    }

    public final BigDecimal getStopLossTotalLoss() {
        return getStopLossPerUnitLoss().multiply(getUnitsToBuy());
    }

    public final BigDecimal getUnitsToBuy() {
        BigDecimal result = getTotalTolerableRiskPerTrade().divide(getStopLossPerUnitLoss(), 0, BigDecimal.ROUND_DOWN);
        if (capital.compareTo(result.multiply(pricePerUnit)) != 1){
            return new BigDecimal(0);
        } else {
            return result;
        }
    }

    public final BigDecimal getTotal() {
        return getUnitsToBuy().multiply(pricePerUnit);
    }

    public static ICapital builder(){
        return new Builder();
    }

    public interface ICapital {
        ITolerableRiskInPercentOfCapitalPerTrade capital(final BigDecimal capital);
    }

    public interface ITolerableRiskInPercentOfCapitalPerTrade {
        IDirection tolerableRiskInPercentOfCapitalPerTrade(final BigDecimal tolerableRiskInPercentOfCapitalPerTrade);
    }

    public interface IDirection {
        IPricePerUnit direction(final Direction direction);
    }

    public interface IPricePerUnit {
        IStopLossPricePerUnit pricePerUnit(final BigDecimal pricePerUnit);
    }

    public interface IStopLossPricePerUnit {
        IBuild stopLossPricePerUnit(final BigDecimal stopLossPricePerUnit);
    }

    public interface IBuild {
        Position build();
    }

    private static class Builder implements ICapital, ITolerableRiskInPercentOfCapitalPerTrade, IDirection, IPricePerUnit, IStopLossPricePerUnit, IBuild {
        private final Position instance = new Position();

        @Override
        public Position build() {
            return instance;
        }

        @Override
        public ITolerableRiskInPercentOfCapitalPerTrade capital(final BigDecimal capital) {
            basicValidate(capital);
            instance.capital = capital;
            return this;
        }

        @Override
        public IDirection tolerableRiskInPercentOfCapitalPerTrade(final BigDecimal tolerableRiskInPercentOfCapitalPerTrade) {
            basicValidate(tolerableRiskInPercentOfCapitalPerTrade);
            if (tolerableRiskInPercentOfCapitalPerTrade.compareTo(new BigDecimal(100)) != -1) {
                throw new IllegalArgumentException("riskInPercent must be lower than 100");
            }
            instance.tolerableRiskInPercentOfCapitalPerTrade = tolerableRiskInPercentOfCapitalPerTrade;
            return this;
        }

        @Override
        public IPricePerUnit direction(final Direction direction) {
            if (direction==null) {
                throw new IllegalArgumentException("argument can't be null");
            }
            instance.direction = direction;
            return this;
        }

        @Override
        public IStopLossPricePerUnit pricePerUnit(final BigDecimal pricePerUnit) {
            basicValidate(pricePerUnit);
            instance.pricePerUnit = pricePerUnit;
            return this;
        }

        @Override
        public IBuild stopLossPricePerUnit(final BigDecimal stopLossPricePerUnit) {
            basicValidate(stopLossPricePerUnit);
            if (instance.direction.equals(Direction.LONG) && instance.pricePerUnit.compareTo(stopLossPricePerUnit) != 1) {
                throw new IllegalArgumentException("price must be higher than stopLossPrice");
            }

            if (instance.direction.equals(Direction.SHORT) && stopLossPricePerUnit.compareTo(instance.pricePerUnit) != 1) {
                throw new IllegalArgumentException("stopLossPrice must be higher than price");
            }
            instance.stopLossPricePerUnit = stopLossPricePerUnit;
            return this;
        }
    }

    protected static void basicValidate(final BigDecimal bigDecimal) {
        if (bigDecimal == null) {
            throw new IllegalArgumentException("argument can't be null");
        }
        if (!(bigDecimal.signum() > 0)) {
            throw new IllegalArgumentException("argument must have positive signum");
        }
    }
}

导致这样的实例化

Position.builder()
        .capital(new BigDecimal(10000))
        .tolerableRiskInPercentOfCapitalPerTrade(new BigDecimal(2))
        .direction(Direction.LONG)
        .pricePerUnit(new BigDecimal(25))
        .stopLossPricePerUnit(new BigDecimal(24))
        .build();

尝试在不同语言之间移植代码并不容易,不能也不应该期待相同的功能。也就是说,有什么方法可以在 JavaScript 中模拟类似的功能吗? (香草或必要时通过一些 modules/libraries)

有几种方法可以做到这一点。

一种选择是以几乎完全相同的方式进行:使用具有指定详细信息的方法的构建器对象和您调用以获取最终对象的 build 方法(或类似方法)。构建对象的结果调用看起来几乎完全相同(模类型名称等)。

另一种选择是利用 JavaScript 的对象初始化器语法(又名 "object literals")将一个 "options" 对象传递给 Position,像这样:

function Position(options) {
    if (/*...the options aren't valid...*/) {
        throw new Error(/*...*/);
    }
    this.capital = options.capital;
    // ...
}

用法:

var p = new Position({
    capital: 10000,
    tolerableRiskInPercentOfCapitalPerTrade: 2,
    direction: Direction.LONG,
    pricePerUnit: 25,
    stopLossPricePerUnit: 24
});

在构造函数中,如果您打算直接使用 options 中的数据作为新实例的属性,您可以使用函数 top 复制它们超过:

function applyOptions(instance, options) {
    Object.keys(options).forEach(function(key) {
        instance[key] = options[key];
    });
    return instance;
}

然后:

function Position(options) {
    if (/*...the options aren't valid...*/) {
        throw new Error(/*...*/);
    }
    applyOptions(this, options);
}

(jQuery,如果你使用它,会有一个 $.extend function that basically does this; Underscore, if you use it, has _.extend and _.extendOwn。)

但是,如果您要在将选项作为属性存储在新实例上之前对其进行一些操作,那么这样的盲目复制并不理想。