HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Building car factory with custom features per car

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
carperfactorywithbuildingcustomfeatures

Problem

I was once asked in an interview to build a factory that makes cars.
All cars has common features like price, rating and color and they have some features like fuel injection which can on specific car but not mandatory. Moreover there can be several types of the same feature like fuel-injection v1, v2 ect, and you should be able to compare these features. Features should be added with ease. (Meaning new feature)

I've also added a general car compare.

I had ~30 min, I didn't have time to finish my idea, but what I had in mind is doing a property of a list of features for my base car type.

I didn't pass and I would like to know two things:

  • Is there something wrong with the approach of List of Feature for each car?



  • Is there something I could do better with my factory design?



This is my code, I have a general CarFactory instead of manufacture based and all models in one file for the simplicity of showing the code:

BL:

Factory.cs

```
using System;
using System.Collections.Generic;
using System.Linq;

namespace BL
{
public class Car
{
public readonly CarTypes Model;
public readonly CarManufactures Manufacture;
public readonly string Color;

private float price;
public float Price
{
get { return price; }
}
internal Car(CarTypes model, CarManufactures manufacture, string color, float price)
{
this.Model = model;
this.Manufacture = manufacture;
this.Color = color;
this.price = price;
this._features = new List();
}
internal void AddFeature(Feature feature)
{
this._features.Add(feature);
this.price += feature.Install();
}

internal List _features;
public List Features
{
get
{
return _features;
}
}

public bool HasFeature(Feature feature)
{
return t

Solution

In 30 min I could only create a draft. It's crazy what they requrie in an interview. It usually takes hours to come up with a resonable design.

Anyways this design has one very bad blocker that prevents it from being extendable. It's the CarManufactures switch. You have a fixed number of those types and you cannot add new ones without modifying the switch and the enum.

I would drop the Family/Sport-Car stuff and replace it with Coupe, Combi, SUV etc as they are the real car types. Those types should be classes derived from the Car so that you can add new ones easily.

Whether a car is a family or a sport car is usually defined by the features it has - sport seats, engine with more power etc. and it's rather subjective.

abstract class Car
{
    protected Car(string make) { ... }
    public string Make { get; }
    public Feature Feature { get; set; }
}

abstract class Combi : Car {}

abstract class Hatchback : Car {}

class FocusCombi : Combi
{
    public Focus() : base("Ford") {}
}

class FocusHatchback : Hatchback
{
    public Focus() : base("Ford") {}
}


With this structure you can create new car types anytime.

The feautures could be created with the composition pattern.

Then I would reduce the factory to just:

public static class CarFactory
{
    public static Car CreateCar(Feature feature) where TCar : Car, new()
    {
        return new TCar() 
        {
            Feature = feature
        };        
    }
}


and use it like this:

CarFactory.CreateCar(Feature.Family);


You could configure the feature set by composition like:

abstract class Feature 
{
    public static Feature Family => Feature.Empty
        .Add()
        .Add();
}


Feature composition

First we need an abstract class for other features and a helper Empty property. ForEach will us allow to get all features.

abstract class Feature
{
    public static Feature Empty => new CompositeFeature();

    public virtual void ForEach(Action feature) { feature(this); }    
}


Next we need some contrete features:

class PowerWindows : Feature { }

class InjectionA : Feature { }


To build a feature chain we need a composite feature that will store the chain and it will also enumerate it:

class CompositeFeature : Feature
{
    private readonly Feature[] _features;

    public CompositeFeature(params Feature[] feautures)
    {
        _features = feautures;
    }

    public override void ForEach(Action feature) 
    { 
        foreach (var f in _features)
        {
            f.ForEach(feature);
        }        
    }
}


With an extension like this we can easily build the chain:

static class FeatureComposition
{
    public static Feature Add(this Feature feature) where TFeature : Feature, new()
    {
        return new CompositeFeature(feature, new TFeature());
    }
}


Usage:

var feature = Feature.Empty
    .Add()
    .Add();

feature.ForEach(x => /* do somehting with x */ );


It's a one big feature that is composed of many other features.

I've added the ForEach so that we can get them all and do something later with each of them.

One of my favourite patterns ;-)

Decorator

You can also build different cars with a decorator pattern instead of an inheritance.

You leave the Car untouched:

abstract class Car
{
    protected Car(string make) { ... }
    public string Make { get; }
    public Feature Feature { get; set; }
}


but then instead of more abstract classes you wrap a Focus as a Combi

class Focus : Car
{
    public Focus() : base("Ford") {}
}

class Combi : Car 
{
    private readonly _car;
    public Combi(Car car) { _car = car; }
}


Example:

var focus = new Focus();
var focusCombi = new Combi(focus);


Finding features

To find a feature I would create an extension like this one:

static class FeatureExtensions
{ 
    public static T Find(this Feature feature) where T : Feature
    {
        var result = default(T);
        feature.ForEach(f =>
        {
            if (f.GetType() == typeof(T)) 
            {
                result = (T)f;
                return;
            }
        });
        return result;
    }
}


Example:

var injectionA = feature.Find();
var injectionB = feature.Find();
...do the comparison


You can virtually add anything you want.

Code Snippets

abstract class Car
{
    protected Car(string make) { ... }
    public string Make { get; }
    public Feature Feature { get; set; }
}

abstract class Combi : Car {}

abstract class Hatchback : Car {}

class FocusCombi : Combi
{
    public Focus() : base("Ford") {}
}

class FocusHatchback : Hatchback
{
    public Focus() : base("Ford") {}
}
public static class CarFactory
{
    public static Car CreateCar<TCar>(Feature feature) where TCar : Car, new()
    {
        return new TCar() 
        {
            Feature = feature
        };        
    }
}
CarFactory.CreateCar<FocusCombi>(Feature.Family);
abstract class Feature 
{
    public static Feature Family => Feature.Empty
        .Add<NinjectInjection>()
        .Add<PowerWindows>();
}
abstract class Feature
{
    public static Feature Empty => new CompositeFeature();

    public virtual void ForEach(Action<Feature> feature) { feature(this); }    
}

Context

StackExchange Code Review Q#139984, answer score: 10

Revisions (0)

No revisions yet.