I have a class where I want to chain certain operations. The class look something like this
class MyClass<T> {
create<X>(fn: (_: T) => X): MyClass<X> {
...
doCreate(fn);
return this as unknown as MyClass<X>;
}
update<O>(x: (_: T) => O): MyClass<O> {
...
doUpdate(x);
return this as unknown as MyClass<O>;
}
validate(x: (_: T) => boolean): MyClass<T> {
...
doValidation(x);
return this;
}
log(str: string): MyClass<T> {
doLog(str);
return this;
}
}
Let's take an example of end-to-end testing where there are multiple operations required to perform a test. And each step needs some sort of validation or logging. Those tests can get quite messy soon.
Imagine having a simple high-level API that does these forms of operations sequentially, like,
- create operation
- performing validations on the result from previous step and passing it forward
- transforming the output from the previous operation to be used in the next create operation
- performing another create operation from the previous step
- validation the output of previous step
- Finishing the sequence
Code representation could be something like the following,
abc.create((x) => "3")
.validate((x) => x instanceof string)
.log("Created a string")
.update(parseInt)
.log("Updated to integer")
.log("Using the number updated in the previous call as an input to create")
.create((x) => x * 3)
.validate((x) => x instanceof number)
.log("Multplying the number by 3")
.update((x) => x + "3")
.done();
I can not achieve the behavior without force casting the instance to another type which I'm doing with the following statement in create
and update
methods.
return this as unknown as MyClass<O>;
Is it a bad practice to force cast the initial type to something else like done above? If so, what would be a better way to achieve the same? Probably, creating a new instance of the class and returning it could be a solution but it includes the overhead of a new object being created.