封装在 OOP 中如何工作

How does encapsulation works in OOP

public interface IShoulders {
    public void move();
}

public class Body {

public void createBody(){
    RightHand rightHand = new RightHand();
    rightHand.move();
    IShoulders leftHand = new LeftHand();
    leftHand.move();
}

}

public class RightHand implements IShoulders {

    public void move(){
        System.out.println("Move Right Hand");          
    }   
}

public class LeftHand implements IShoulders {

public void move(){
    System.out.println("Move Left Hand");           
}   
}
public class Main {

public static void main(String[] args){

    Body myBody1 = new Body();
    myBody1.createBody();


    Body myBody2 = new Body();
    myBody2.createBody();
}

}

请在 class body 中解释当使用 IShoulder leftHand = new LeftHand();RightHand rightHand = new RightHand(); 时封装有何不同他们都在做同样的事情所以什么时候使用什么,为什么重要?谢谢

归根结底,当访问一个字段时,您会立即输入它。在这种情况下,当您将一个字段键入为接口时:IShoulders,您只能直接访问接口定义的任何 properties/methods。当您访问定义为实现相同接口的 class 的字段时,这个定义和访问为 RightHand 的新字段不仅提供对 [=16= 定义的 properties/methods 的访问] 但也扩展了 properties/methods 由 class RightHand.

定义

根据您的定义,除了 move() 打印出的值外,这里没有真正的区别,但需要进行以下更改:

public class RightHand implements IShoulders {

    public void move(){
        System.out.println("Move Right Hand");          
    }
    public void Gesture()
    {
        System.out.println("Make a gesture with Right Hand");
    }   
}

新的 Gesture 方法只能被定义为 RightHand 的字段访问,它不会存在于定义为只有 IShoulders 的字段上,即使您将使用 new RightHand() 的字段,您需要将字段装箱或转换为 RightHand 才能访问新方法。

鉴于上述 RightHand class 定义,请尝试以下操作:

IShoulders shoulder = new RightHand();
shoulder.move(); // this will work fine...
shoulder.Gesture(); // this will not be possible as "Gesture()" does not exist on IShoulders.
((RightHand)shoulder).Gesture(); // this will work after casting shoulder as RightHand
                                 // since the underlying object is actually a RightHand.

然后尝试以下操作:

RightHand rhand = new RightHand();
rhand.move(); // again, this works fine...
rhand.Gesture(); // this now works fine too..

此外,该接口是您与实现它的任何 class 的合同。它只是说,如果 class 实现了一个接口,它必须实现该接口中的所有内容。但是,它没有说明应该如何实施。所以,只要 class 实现了接口的结构,那些实现的内容就可以完全为每个人定制 class。

实现接口的替代方法是使用子 class 创建派生的 class。这看起来类似于实现接口,但是基础 class 已经具有属性和方法的实现。它们可以被覆盖,除非您不使用 virtual 装饰它们,或者在另一个继承级别覆盖并使用 final 装饰。

I;m sorry but lets tone this down as its still a little confusing. When i write IShoulder rightHand or RightHand rightHand i;m defining the type of the object before creating it? Also i always wanted to ask why we need to define a type if we are using the new keyword anyway like we do with any other objects like of collections? list<> mylist = new list<> and how to address this verbally? like object rightHand of class RightHand encapsulate RightHand/IShoulder? correct me if iam wrong – eersteam

当然,至于键入变量 - 这允许编译器(和程序员,就此而言)强烈定义可以使用的内容,以及如何在 object/value 方面使用它。一个简单的例子是一个函数:

public string HyphenateTwoWords(string word1, string word2)
{
   return string.Format("{0}-{1}", word1, word2);
}

上述函数采用两个 string 参数并将它们连接在一起,并在它们之间使用连字符。如果参数的类型不是 string,那么任何东西都可以传入,结果在某些情况下可能只是错误的,或者在其他情况下抛出异常。由于它们被定义为 string,因此 string.Format() 调用执行时没有任何问题。

现在,在 C# 和其他语言中,您实际上可以使用 var 通用地定义一个 field/variable,例如:

List<string> myStringList = new List<string>();
myStringList.Add("Test1");
myStringList.Add("Test2");
myStringList.Add("Test3");
foreach(var item in myStringList)
   Console.WriteLine(item);

这让编译器可以根据分配给它的内容推断出 item 的实际类型。但是,在大多数情况下,您并不真的想使用 var。如果没有其他原因,最好明确键入变量,以提高可读性。查看变量的类型要容易得多,尤其是当您使用由多个开发人员维护的代码时。此外,显式键入变量可确保按预期使用,否则会抛出可处理的异常,或者编译器会在构建解决方案时抛出错误或警告。

现在,关于您上面的评论:

...how would i know if its left hand or right hand? and in my example hands are not shoulders but hands are suppose to be attached to the should because shoulder makes them capable of moving. So how would you implement this? – eersteam

让我们设计一些接口和 classes 来说明它们的用途,我会做一些 changes/additions 你已经拥有的所有这些应该是相当直接的 - 看看 [= Body class 上的 36=] 方法并查看 LeftLeg class。

public class BodyConstructor
{
    public interface IBody
    {
        int Age { get; set; }
        string Species { get; set; }
        BodyPartGroup Head { get; set; }
        BodyPartGroup Thorax { get; set; }
        BodyPartGroup Abdomen { get; set; }
        BodyPartGroup Pelvis { get; set; }
    }

    public interface IBodyPartGroup
    {
        BodyPartGroupType Type { get; set; }
        List<IBodyPart> BodyParts { get; set; }
    }

    public interface IBodyPart
    {
        LateralDirection LateralOrientation { get; set; }
        string Name { get; set; }
        List<IBodyPart> SubParts { get; set; }
        void Move();
    }

    public enum BodyPartGroupType
    {
        Head = 1,
        Thorax = 2,
        Abdomen = 3,
        Pelvis = 4,
    }

    public enum LateralDirection
    {
        Central = 0,
        Right = 1,
        Left = 2,
    }



    public class Body : IBody
    {
        public Body()
        {
            Head = new BodyPartGroup(BodyPartGroupType.Head);
            Thorax = new BodyPartGroup(BodyPartGroupType.Thorax);
            Abdomen = new BodyPartGroup(BodyPartGroupType.Abdomen);
            Pelvis = new BodyPartGroup(BodyPartGroupType.Pelvis);
        }
        public Body(int age, string species)
            : this()
        {
            Age = age;
            Species = species;
        }
        public int Age { get; set; }
        public string Species { get; set; }
        public BodyPartGroup Head { get; set; }
        public BodyPartGroup Thorax { get; set; }
        public BodyPartGroup Abdomen { get; set; }
        public BodyPartGroup Pelvis { get; set; }

        public void AddLimbs()
        {
            AddArms();
            AddLegs();
        }

        public void AddArms()
        {
            // Individual segments for the left arm
            BodyPart leftShoulder = new BodyPart("Left Shoulder", LateralDirection.Left);
            BodyPart leftArm = new BodyPart("Left Arm", LateralDirection.Left);
            BodyPart leftHand = new BodyPart("Left Hand", LateralDirection.Left);
            leftArm.SubParts.Add(leftHand);
            leftShoulder.SubParts.Add(leftArm);
            // Individual segments for the right arm
            BodyPart rightShoulder = new BodyPart("Right Shoulder", LateralDirection.Right);
            BodyPart rightArm = new BodyPart("Right Arm", LateralDirection.Right);
            BodyPart rightHand = new BodyPart("Right Hand", LateralDirection.Right);
            rightArm.SubParts.Add(rightHand);
            rightShoulder.SubParts.Add(rightArm);

            // Add arms to thorax
            Thorax.BodyParts.Add(rightShoulder);
            Thorax.BodyParts.Add(leftShoulder);

        }

        public void AddLegs()
        {
            // Individual segments for the left leg
            BodyPart leftHip = new BodyPart("Left Hip", LateralDirection.Left);
            LeftLeg leftLeg = new LeftLeg(); // Here we use the LeftLeg class instead, which inherits BodyPart
            BodyPart leftFoot = new BodyPart("Left Foot", LateralDirection.Left);
            leftLeg.SubParts.Add(leftFoot);
            leftHip.SubParts.Add(leftLeg);

            //Individual segments for the right leg
            BodyPart rightHip = new BodyPart("Right Hip", LateralDirection.Right);
            BodyPart rightLeg = new BodyPart("Right Leg", LateralDirection.Right);
            BodyPart rightFoot = new BodyPart("Right Foot", LateralDirection.Right);
            rightLeg.SubParts.Add(rightFoot);
            rightHip.SubParts.Add(rightLeg);

            // Add legs to pelvis
            Pelvis.BodyParts.Add(leftHip);
            Pelvis.BodyParts.Add(rightHip);
        }
    }

    public class BodyPartGroup : IBodyPartGroup
    {
        public BodyPartGroup()
        {
            BodyParts = new List<IBodyPart>();
        }
        public BodyPartGroup(BodyPartGroupType type)
            : this()
        {
            this.Type = type;
        }
        public BodyPartGroupType Type { get; set; }
        public List<IBodyPart> BodyParts { get; set; }
    }

    public class BodyPart : IBodyPart
    {
        public BodyPart()
        {
            SubParts = new List<IBodyPart>();
        }
        public BodyPart(string name, LateralDirection orientation)
            : this()
        {
            Name = name;
            LateralOrientation = orientation;
        }
        // Location of body part: Left, Central, Right
        public LateralDirection LateralOrientation { get; set; }
        public string Name { get; set; }
        public List<IBodyPart> SubParts { get; set; }
        public virtual void Move()
        {
            // Common body part movement code.
        }
    }

    public class LeftLeg : BodyPart
    {
        public LeftLeg()
        {
            Name = "Left Leg";
            LateralOrientation = LateralDirection.Left;
        }
        public override void Move()
        {
            // Custom left leg move code

            base.Move(); // Call base BodyPart.Move();
        }
    }
}

在您的示例中,leftHand 是 LeftHand,rightHand 是 RightHand。您创建的 objects 都是实现 IShoulder 的 classes 的实例。两者都是 Ishoulders,因此可以 "move"。 leftHand 和 rightHand 变量 的区别在于,您可以将 leftHand 设置为 RightHand class 的实例,因为 leftHand 被声明为 IShoulder,两者都可以。如果你这样做,然后调用 leftHand.move(),它会移动右手。明白了吗?

该界面让您能够使用任何一只手工作,而无需知道您实际使用的是哪只手。在您的示例中,如果您将 IShoulder 接口命名为 IHand,然后将 IHand 变量命名为 hand 而不是 leftHand,这将更有意义。你的例子暗示手是肩膀。

但这不是封装的意义所在。封装只是将相关数据和作用于该数据的方法放在一起,并限制外部访问仅有助于其基本特征的组件(即隐藏细节)。所以 Body class 可能包含头部、手臂、腿、躯干等。而手臂可能包含肩膀、上臂、肘部、下臂和手。从外面看,您所看到的只是一只手臂,其零件可以做您期望手臂做的事情。您应该在不知道或不关心它如何工作的情况下使用 Arm class。这是一个例子:

// the arm class encapsulates the parts of the arm (the data), and the behavior of the arm (the methods).
class Arm
{    
    Shoulder shoulder = new Shoulder();
    Hand hand = new Hand();
    UpperArm upperArm = new UpperArm();
    Elbow elbow = new Elbow();
    LowerArm lowerArm = new LowerArm();

    void poke()
    {         
        flinch();
    }

    void flinch()
    {
        // write some code that flinches the arm
    }

    void move()
    {
         // move the arm all over the place 
         shoulder.move();
         hand.wave(); 
         doHighFive();
         //...  
    }

    void doHighFive()
    {
         // move all the arm parts in a high-five motion
    }
}



// the Body class encapsulates the parts of the body (the data), and the behavior of the body (the methods).
class Body
{    
    Head head = new Head();
    Torso torso = new Torso();
    Arm leftArm = new Arm();
    Arm rightArm = new Arm();
    Leg leftLeg = new Leg();
    Leg rightLeg = new Leg();

    void poke()
    {         
        flinch(); 
        dance();
        System.out.println("I didn't flinch, I was just dancing! Now I'm tired, though."); 
        goToBed();
    }

    void flinch()
    {
        // write some code that makes the body flinch
    }

    void dance()
    {
         // write code to make the body dance
    }

    void goToBed()
    {
         // write code to make the body go to bed
    }
}

不过,您的问题似乎更多地是关于何时使用接口而不是实现该接口的 classes 之一。这更多是关于抽象数据类型的问题,而不是封装。抽象数据类型仅由其行为定义(无代码或数据)。接口是定义抽象数据类型的一种方式。它只是一个方法签名列表;将其视为实施者必须如何行事的合同。

那么你什么时候会使用界面?举个例子,如果我的工作是遍历 collection 个 body 部分和 "poke" 个部分,我真的不在乎你 body 部分的具体类型给我,我只需要戳他们。这是一个例子:

// IBodyPart defines an abstract data type that can be "poked", among other things.
interface IBodyPart
{
    void poke();
    //...
}

// maybe hands handle being poked by slapping
class Hand implements IBodyPart
{
    void poke()
    {         
        slap();
    }

    void slap()
    {
        // write some code that slaps
    }
}

// maybe shoulders just flinch
class Shoulder implements IBodyPart
{
    void poke()
    {         
        flinch();
    }

    void flinch()
    {
        // write some code to flinch the shoulder
    }
}

// etc. Each IBodyPart class has to implement the poke method.

// I don't know nor do I care what I'm poking. I can do my job easily thanks to abstract data types!
class Me
{
    void PokeEmAll(List<IBodyPart> bodyPartList)
    {
        for (IBodyPart bodyPart : bodyPartList)
        {
            bodyPart.poke();
        }
    }
}

class Main
{
    // create a list of body parts so I can poke 'em all
    static void main()
    {
        List<IBodyPart> bodyPartList = new List<IBodyPart>();
        Hand leftHand = new Hand();
        bodyPartList.add(leftHand);
        Hand rightHand = new Hand();
        bodyPartList.add(rightHand);
        Shoulder leftShoulder = new Shoulder();
        bodyPartList.add(leftShoulder);
        Shoulder rightShoulder = new Shoulder();
        bodyPartList.add(rightShoulder);  

        Me me = new Me();
        me.PokeEmAll(bodyPartList);
    }
}