2

How do I create instances of classes which require a huge amount of components and attributes? Take for example a car. A car has hundreds of sub components each with their own specific properties. There would be thousands of parameters to pass or work with. Some components even repeat themselves inside of the car (wheels, doors, brakes, springs, etc). I made two demo Car classes and I came up with two ways of initializing them. I am not happy with either.

First Way: Nest all of the subclasses and initialize the car with a huge statement. I don't like this because I cannot create sub components first and slowly build. What if I don't know how big the wheels are going to be, what do I do? Do I need to specify defaults? What If I need multiple doors? Would this be usable if the class actually needs thousands of arguments like a real car would?

Class Car(Size, TireType, Displacement, Cylinders, ECU, Fuel Type, PowerLockButton, Gears, ShiftLeverLength, ClutchType, GearKnob, Computer, TorqueConverter, FluidType, Links, SpringRate, BushingMaterial, ShockType)
    Class Wheels(Size, TireType)
    Class Engine(Displacement, Cylinders, ECU, Fuel Type)
    Class Door(PowerLockButton)
    Class Transimission(Gears)
    Class ManaulTransmission Inherits From Transmission(ShiftLeverLength, ClutchType, GearKnob)
    Class AutomaticTransmission Inherits From Transimission(Computer, TorqueConverter, FluidType)
    Class Suspension(Links, SpringRate, BushingMaterial, ShockType)

FordMustang = Car(20", Michelin, 5.0L, 8Cylinders, Delphi, Gasoline, NoPowerLock, 6Gears, 3inches, SportClutch, MetalKnob, StiffLinks, 550lbRate, PolyUrethaneBushing, MagneticShocks)
BMW530i = Car(18", Continental, 3.0L, 6Cylinders, Bosch, Gasoline, YesPowerLock, 6Gears, BoschTransComputer, MediumTorqueConverter, ExpensiveFluid, ModerateLinks, 45lbRate, RubberBushing, SachsShocks)

Second Way: Create the Car component classes and then aggregate them all into a Car Class. I don't like this because I have to use many lines of code to build the car and that code seems orphaned sitting out in the open. Also, I am never going to use any of these components (wheel, engine, transmission, etc...) in anything other than inside of a car, so why leave them outside of the scope of the car class?

Class Wheels(Size, TireType)
Class Engine(Displacement, Cylinders, ECU, Fuel Type)
Class Door(PowerLockButton)
Class Transimission(Gears)
Class ManaulTransmission Inherits From Transmission(ShiftLeverLength, ClutchType, GearKnob)
Class AutomaticTransmission Inherits From Transimission(Computer, TorqueConverter, FluidType)
Class Suspension(Links, SpringRate, BushingMaterial, ShockType)

Class Car(Wheels, Engine, Door, Transimission, Suspension)

BMW530iWheels = Wheels(18', Continental)
BMW530iEngine = Engine(3.0L, 6Cylinders, Bosch, Gasoline)
BMW530iDoor = Door(YesPowerLock)
BMW530iTransmission =  AutomaticTransmission(6Gears, BoschTransComputer, MediumTorqueConverter, ExpensiveFluid)
BMW530iSuspension = Suspension(ModerateLinks, 45lbRate, RubberBushing, SachsShocks)

BMW530i = Car(BMW530iWheels, BMW530iEngine, BMW530iDoor, BMW530iTransmission, BMW530iSuspension)

Any insight on how this can be done reliably? My concern is that either way I pick will become overwhelming to use when the number of attributes is extremely high. What ever you can contribute will be greatly appreciated.

  • 2
    Possible duplicate of How can you decompose a constructor? – gnat Feb 1 '17 at 4:42
  • 1
    @gnat: I don't think this is a duplicate. That question is about how to take a constructor with primitive values and make the argument list shorter (essentially). This question starts out where the proposed duplicate leaves off. Basically, after reading that question, one would arrive at the code the OP has posted in this question, and the OP still wants to see some improvements in maintainability and clarity. – Greg Burghardt Feb 3 '17 at 17:46
  • @GregBurghardt as you can see second top answer in the duplicate suggests the same as top answer over here. Given its high score it appears that many readers would really disagree with your assessment about where it "stops" since they appreciated a fuller solution – gnat Feb 3 '17 at 17:50
  • @gnat: Hm. Kind of a tough call. The top rated answer here really should be on that other question. The answer you refer to basically says "you should use Buzzword" followed by a short description, with no real example or further explanation. – Greg Burghardt Feb 3 '17 at 17:52
  • my point was not so much about answer quality as about your reading of the duplicate question - votes on the answer I referred suggest that your interpretation is somewhat narrow compared to many other readers @GregBurghardt – gnat Feb 3 '17 at 17:55
4

Use the Builder pattern

The Builder Pattern is intended for exactly this scenario.

Named parameters

In this example I combine the builder pattern with named parameters to make it crystal clear what is being built:

var builder = new CarBuilder();
builder.WithWheels(17, WheelType.Aluminum);
builder.WithEngine(displacement: 2.1F, 
                   cylinders:    4, 
                   ecu:          EcuType.Programmable, 
                   fuel:         FuelType.Unleaded);
builder.WithDoors(powerlock: true);
var car = builder.Build();

Fluent syntax

If each method ends with return this then you can chain them together using fluent syntax:

var car = new CarBuilder().WithWheels(17, WheelType.Aluminum)
                          .WithEngine(displacement: 2.1F, 
                                      cylinders:    4, 
                                      ecu:          EcuType.Programmable, 
                                      fuel:         FuelType.Unleaded)
                          .WithDoors(powerlock: true)
                          .Build();

As a factory

Another feature of this pattern is that the Build() method can return any class that inherits from Car; thus you can extend and specialize Car (e.g. into PassengerCar, Truck, etc). This allows it to work as a factory as well.

class CarBuilder()
{
    public Car Build()
    {
        Car newInstance;
        if (_wheels == 18) newInstance = new Truck()
        else if (_wheels == 4 && _powerLock) newInstance = new LuxuryCar()
        else newInstance = new PassengerCar();

        return newInstance;
    }
}
1

I would encourage you toward a more data oriented approach instead of using new code (new classes) for every little part.

For something this complex, I would build to a plan with user selected options.

The plans are there to ensure that a usable car comes out the other end.

Plans can range from simple parts lists, to not just listing parts but also capturing hierarchically composed build/assembly details. Further, many available configuration options have rules (not compatible with this or that, requires this or that...).

There are numerous large, professional software offerings designed to help with these things.

If you were interested creating some of this capability in house, I would recommend creating a formal notion of a "plan" using some form of generic parts list and rules for the various options, and then creating tooling around working with plans and generic parts.

Then your software tasks would be to (1) support the creation and validation of plans, (2) present the plans with their available options in some user interface, (3) capture a selected base plan with specifically chosen options, (4) instantiate an order (or something else) from those plans with chosen options.

If you go about it the way you're describing above in the question, your car plans will be captured only in the code of specialized classes (rather than as information that could otherwise be manipulated), which is what we'd refer to as hard-coded and be a maintenance problem.

How do I create instances of classes which require a huge amount of components and attributes?

I wouldn't do this manually (i.e. by writing custom code for each scenario). I'd instead create generic capability to instantiate what I need from chosen plans with their chosen options, which would give me a parts list, from which any desirable objects or orders could be made. (If I needed objects, I'd reuse a generic parts class that would be able to represent any given part.)

Would this be usable if the class actually needs thousands of arguments like a real car would?

I don't believe that either of the ways you're suggesting would work for a real car, rather you'd want some way to communicate available plans & options and then selections.

Also, I am never going to use any of these components (wheel, engine, transmission, etc...) in anything other than inside of a car, so why leave them outside of the scope of the car class?

In the automotive industry, the individual parts will need to be ordered, sourced, repairable, etc... So, these parts will be used outside of the "car". However, I reiterate that you look more toward capturing the notion of plans and parts as data rather than as code (classes).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.