In design patterns, there are three patterns with the word "Factory" in their names: Simple Factory Pattern, Factory Method Pattern, and Abstract Factory Pattern. Let's demonstrate them using TypeScript and Rust respectively:
Simple Factory Pattern
I heard that the NBA has a specialized factory for producing point guards, and players like Chris Paul and Steve Nash came from there. Here's how you can implement it using the Simple Factory Pattern:
TypeScript
interface PointGuard {
assist(): void
}
class Paul implements PointGuard {
assist(): void {
console.log('Paul assist')
}
}
class Nash implements PointGuard {
assist(): void {
console.log('Nash assist')
}
}
class SimplePointGuardFactory {
createPointGuard(pointGuardType: string): PointGuard {
switch (pointGuardType) {
case 'P':
return new Paul()
case 'N':
return new Nash()
default:
throw new Error('Invalid point guard')
}
}
}
const factory: SimplePointGuardFactory = new SimplePointGuardFactory()
const paul: PointGuard = factory.createPointGuard('P')
paul.assist()
const nash: PointGuard = factory.createPointGuard('N')
nash.assist()
Rust
pub trait PointGuard {
fn assist(&self);
}
struct Paul;
impl PointGuard for Paul {
fn assist(&self) {
println!("Paul assist");
}
}
struct Nash;
impl PointGuard for Nash {
fn assist(&self) {
println!("Nash assist");
}
}
pub struct SimplePointGuardFactory;
impl SimplePointGuardFactory {
pub fn new() -> Self {
Self {}
}
pub fn create_point_guard(&self, point_guard_type: &str) -> Box<dyn PointGuard> {
match point_guard_type {
"P" => Box::new(Paul),
"N" => Box::new(Nash),
_ => panic!("Invalid point guard"),
}
}
}
fn main() {
let factory = SimplePointGuardFactory::new();
let paul = factory.create_point_guard("P");
paul.assist();
let nash = factory.create_point_guard("N");
nash.assist();
}
The concept of the Factory Method Pattern is relatively simple. The drawback of the Simple Factory Pattern is that when a new point guard needs to be added, we have to modify the SimplePointGuardFactory class. Additionally, as the number of point guards increases, the code in the factory becomes extremely large. That's why the Factory Method Pattern was introduced.
Factory Method Pattern
TypeScript
interface PointGuard {
assist(): void
}
class Paul implements PointGuard {
assist(): void {
console.log('Paul assist')
}
}
class Nash implements PointGuard {
assist(): void {
console.log('Nash assist')
}
}
interface PointGuardFactory {
create_point_guard(): PointGuard
}
class PaulFactory implements PointGuardFactory {
create_point_guard(): PointGuard {
return new Paul()
}
}
class NashFactory implements PointGuardFactory {
create_point_guard(): PointGuard {
return new Nash()
}
}
function assist(factory: PointGuardFactory) {
const pointGuard = factory.create_point_guard()
pointGuard.assist()
}
assist(new PaulFactory())
assist(new NashFactory())
Rust
pub trait PointGuard {
fn assist(&self);
}
pub struct Paul;
impl PointGuard for Paul {
fn assist(&self) {
println!("Paul assist");
}
}
struct Nash;
impl PointGuard for Nash {
fn assist(&self) {
println!("Nash assist");
}
}
pub trait Factory {
fn create_point_guard(&self) -> Box<dyn PointGuard>;
}
pub struct PaulFactory;
impl Factory for PaulFactory {
fn create_point_guard(&self) -> Box<dyn PointGuard> {
Box::new(Paul)
}
}
pub struct NashFactory;
impl Factory for NashFactory {
fn create_point_guard(&self) -> Box<dyn PointGuard> {
Box::new(Nash)
}
}
fn assist(factory: &dyn Factory) {
let point_guard = factory.create_point_guard();
point_guard.assist();
}
fn main() {
assist(&PaulFactory);
assist(&NashFactory);
}
Now, adding a new point guard only requires adding a new guard class and its corresponding factory class. However, if we want to create a team, both of these approaches are not suitable because the factory classes in these patterns are specific to a certain type of product. We need a factory that can create multiple types of products. This is where the Abstract Factory Pattern comes into play.
Abstract Factory Pattern
For simplicity, let's assume that a team only needs a point guard and a centre forward. The implementation would be as follows:
TypeScript
interface PointGuard {
assist(): void
}
class Paul implements PointGuard {
assist(): void {
console.log('Paul assist')
}
}
class Nash implements PointGuard {
assist(): void {
console.log('Nash assist')
}
}
interface CentreForward {
slamDunk(): void
}
class ONeal implements CentreForward {
slamDunk(): void {
console.log('ONeal slam dunk')
}
}
class YaoMing implements CentreForward {
slamDunk(): void {
console.log('YaoMing slam dunk')
}
}
interface TeamFactory {
createPointGuard(): PointGuard
createCentreForward(): CentreForward
}
class RocketTeam implements TeamFactory {
createCentreForward(): CentreForward {
return new YaoMing()
}
createPointGuard(): PointGuard {
return new Paul()
}
}
class LakersTeam implements TeamFactory {
createCentreForward(): CentreForward {
return new ONeal()
}
createPointGuard(): PointGuard {
return new Nash()
}
}
function play(teamFactory: TeamFactory) {
const centreForward = teamFactory.createCentreForward()
const pointGuard = teamFactory.createPointGuard()
pointGuard.assist()
centreForward.slamDunk()
}
play(new RocketTeam())
play(new LakersTeam())
Rust
pub trait PointGuard {
fn assist(&self);
}
pub struct Paul;
impl PointGuard for Paul {
fn assist(&self) {
println!("Paul assist");
}
}
struct Nash;
impl PointGuard for Nash {
fn assist(&self) {
println!("Nash assist");
}
}
pub trait CentreForward {
fn slam_dunk(&self);
}
pub struct ONeal;
impl CentreForward for ONeal {
fn slam_dunk(&self) {
println!("ONeal slam dunk");
}
}
struct YaoMing;
impl CentreForward for YaoMing {
fn slam_dunk(&self) {
println!("YaoMing slam dunk");
}
}
pub trait TeamFactory {
fn create_point_guard(&self) -> Box<dyn PointGuard>;
fn create_centre_forward(&self) -> Box<dyn CentreForward>;
}
pub struct LakersFactory;
impl TeamFactory for LakersFactory {
fn create_point_guard(&self) -> Box<dyn PointGuard> {
Box::new(Paul)
}
fn create_centre_forward(&self) -> Box<dyn CentreForward> {
Box::new(ONeal)
}
}
pub struct RocketFactory;
impl TeamFactory for RocketFactory {
fn create_point_guard(&self) -> Box<dyn PointGuard> {
Box::new(Nash)
}
fn create_centre_forward(&self) -> Box<dyn CentreForward> {
Box::new(YaoMing)
}
}
fn play(team_factory: &dyn TeamFactory) {
let point_guard = team_factory.create_point_guard();
let centre_forward = team_factory.create_centre_forward();
point_guard.assist();
centre_forward.slam_dunk();
}
fn main() {
play(&RocketFactory);
play(&LakersFactory);
}
The above implementation is just a straightforward example. In actual situations, it's important to be flexible and adapt accordingly.