简体   繁体   中英

Class Design Question

Let's say I want to make a HumanBody class. And I want to store the length of each limb.

HumanBody.LeftArmLength = 14;
HumanBody.RightArmLength = 14.1;
HumanBody.LeftLegLength = 32;
HumanBody.RightLegLength = 31.9;

That's all well and good, but it seems like it would be better to do something like:

HumanBody.Arm.Left.Length = 14;
HumanBody.Arm.Right.Length = 14.1;
HumanBody.Leg.Left.Length = 32;
Humanbody.Leg.Right.Length = 31.9;

So this would involve making sub classes. Is what I'm describing something that is considered a "Good Practice"? It seems like this is a more organized way to store data.

Edit: this example is quite simple, but if there are 100 different pieces of data to store, this seems like a much better method to use.

Left and right Arms are instances of Arm so perhaps:

HumanBody.LeftArm.Length = 14; 
HumanBody.RightArm.Length = 14.1; 
HumanBody.LeftLeg.Length = 32; 
Humanbody.RightLeg.Length = 31.9; 

Presumably, you need to consider cases where someone might not have both arms or legs.

[Note: As was noted in the comments, these should ideally be set in a constructor, rather than using properties. This is a general principle: Construct objects in a coherent, usable state.]

It depends on the real situation. Here there doesn't seem to be very much point in grouping the two arms together and the two legs together... the concept of "a pair of arms" isn't generally a useful one.

On the other hand, if you were talking about grouping "DeliveryAddress1", "DeliveryAddress2", "DeliveryTown", "DeliveryZipCode", "BillingAddress1", "BillingAddress2", "BillingTown", and "BillingZipCode" into two instances of an Address class, that's a different matter.

Basically, do the individual parts belong together as part of a natural grouping? Is this group something you might want to handle in a composite manner? Could you see yourself passing the group to some other piece of code? If so, that sounds like a good unit of encapsulation. If not, maybe you're just complicating things unnecessarily...

You might have

HumanBody.LeftArm.Length

where your classes would be something like:

public class HumanBody
{
    public Arm LeftArm { get; private set; }
}

public class Arm
{
    public double Length { get; private set; }
}

But I don't see the advantage in your arm property

public class HumanBody
{
    public Arms Arm { get; private set; }
}

public class Arms
{
    public Arm Left { get; private set; }
    public Arm Right { get; private set; }
}

public class Arm
{
    public double Length { get; private set; }
}

I don't see how that model matches a useful design of a human body (are you ever going to say to another object "here's the two arms of this humanbody"?). Basically there is no reason to compose objects from smaller parts unless that parts are a sensible units in and off themselves. There is an argument for an arm being a separate class, a pair of arms less so.

I think it also depends on the depth and width of your hierarchy. In the example I've given above there is no real gain. You've replaced one double property with a class that contains a single double, maybe slightly more legible, but certainly no more functional. On the other hand, a situation like:

HumanBody.LeftArm.Forefinger.Length = 3.2

Lends itself to composition over the much less legible (and less functional)

HumanBody.LeftArmsForefingerLength = 3.2

What is your primary object? It appears to me that it's the HumanBody and that appendage lengths are simply attributes of that.

By all means have an Arms collection if you think you're going to find a lot of humans with more than two arms :-)

But I think you'll be able to get away with your first solution for your given example. Once an object starts getting hundreds of attributes then, yes, you should think about organising it in a more hierarchical fashion. As to the best way to do that, we'd have to see your actual requirement.

There is no broad sweeping rule for choosing either one. The decision really boils down to whether the attributes really are associated with the main class or whether you think you will need to divide attributes into separate classes so that those classes can be passed around and manipulated on their own.

By the way, I know your examples are rather contrived, but the second one has the implication that the HumanBody has only one arm and only one leg both with a left side and a right side. In this particular case it might be better to have RightArm , LeftArm , RightLeg , and LeftLeg properties that return their respective class instances. Of course that brings you back to the first example if the only attribute you want associated with limbs is their length...just food for thought.

Setting up your object hierarchy depends on what you're needs are. Sometimes it makes sense, sometimes it doesn't.

For example:

//This make sense
public class Person {

    public String FirstName { get; set; }
    public String LastName { get; set; }
    public String FullName
    {
        get { return String.Format("{0} {1}", FirstName, LastName); }
    }

    public Address Address { get; set; }
    public PhoneNumber[] ContactNumbers { get; set; }
}

//This does not make as much sense, but it's still possible.
public class Person {
    public Name Name { get; set; }
    public Address Address { get; set; }
    public PhoneNumber[] ContactNumbers { get; set; }
}

The second example doesn't make as much sense because the Name object just doesn't seem to be they type of object that will get reused, but it's just a container within a container. So it's ok to do that, but personally to me I'd do the first method.

It depends on how you use the laterality. If you had functions that needed to operate on a whole side of the body at once, you could have something like this:

class Bilateral
{
    Arm arm;
    Leg leg;
    Eye eye;
    Ear ear;
    ...
}

class HumanBody
{
    Bilateral left, right;
    Liver liver;
}

So you would do something like HumanBody.liver.weight = 3.14; or HumanBody.left.arm.length = 12.34; .

I think it is shortsighted to limit the human body to a left and right arm. Some people have no arms. Some people have only a right arm. What if a person's arm comes more from the front?

Of course we can apply these principles to legs as well.

With regard to limiting lengths to the constructor, are prostheses taken into account?

I think that composition makes the most sense here, however I'd recommend dynamic composition over static composition in order to account for anatomical anomalies (missing limbs, etc)

public abstract class Body
{
   public Dictionary<string, BodyPart> BodyParts { get; set; }
   //other properties, methods, etc.
}

Your BodyPart class might be purely virtual...

public abstract class BodyPart
{
}

You can add specific types of body parts:

public abstract class Limb : BodyPart
{
   public float Length { get; set; }
   public abstract void Move();
}

If you need to, create subclasses for arms, legs, etc

public class Arm : Limb
{
   public override void Move()
   {
      Debug.WriteLine("Arm is moving");
   }
}

Finally, you can subclass your body to be human-specific:

public class HumanBody : Body
{
   //properties and methods specific to the human body
}

Putting it all together, you can create a robust HumanBody dynamically:

HumanBody body = new HumanBody();
Arm leftArm = new Arm(){ Length=24F };
Arm rightArm = new Arm(){ Length=24F };
//create other parts
body.BodyParts.Add(new KeyValuePair<string, BodyPart>("LeftArm",leftArm));
body.BodyParts.Add(new KeyValuePair<string, BodyPart>("RightArm",rightArm));
//add other parts

While this is very flexible, the obvious disadvantage is that you'll have to do a fair amount of casting if you actually want to use any of the body parts:

Arm leftArm = body.BodyParts["LeftArm"] as Arm;
leftArm.Move();

Not bad for 2 lines of code, but should you need to coordinate movements the added overhead of casting could be too onerous.

That's my 2c.

Just to put my two cents in I vote we acquiesce to the natural bisymmetry of a human:

public class Digit { public double Length { get; set; } }

public class HumanSide
{
    public Digit Arm { get; set; }
    public Digit Leg { get; set; }
    public Digit MiddleFinger { get; set; }
    public Digit Foot { get; set; }
    public Digit Nose { get; set; }
}

public class Human
{
    public HumanSide Right { get; set; }
    public HumanSide Left { get; set; }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM