【问题标题】:How does encapsulation works in OOP封装在 OOP 中是如何工作的
【发布时间】:2016-05-14 13:46:25
【问题描述】:
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();
}

}

请在课堂body 中解释当IShoulder leftHand = new LeftHand();RightHand rightHand = new RightHand(); 相比时,封装有何不同,它们都在做同样的事情,那么何时使用什么以及为什么重要?谢谢你

【问题讨论】:

    标签: java c# oop encapsulation


    【解决方案1】:

    归根结底,当访问一个字段时,您会在立即键入时访问它。在这种情况下,当您有一个字段类型为接口时:IShoulders,您只能直接访问接口定义的任何属性/方法。当您访问一个定义为实现相同接口的类的字段时,这个新字段(定义和访问为RightHand)不仅提供对IShoulders 定义的那些属性/方法的访问,还提供对由定义的扩展属性/方法的访问班级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 类定义,请尝试以下操作:

    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..
    

    此外,接口是实现它的任何类的约定。它只是说,如果一个类实现了一个接口,它必须实现该接口中的所有内容。然而,它没有说明它应该如何实施。因此,只要类实现了接口的结构,这些实现的内容就可以完全定制到每个单独的类。

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

    我很抱歉,但让我们缓和一下,因为它仍然有点令人困惑。 当我写 IShoulder rightHand 或 RightHand rightHand 时,我正在定义 创建对象之前的类型?我也一直想问 如果我们仍然使用 new 关键字,为什么我们需要定义一个类型 就像我们对任何其他对象(如集合)所做的那样?列表 mylist = new list 以及如何口头解决这个问题?像对象右手的 RightHand 类封装 RightHand/IShoulder?如果我纠正我 错了——eersteam

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

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

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

    现在,在 C# 和其他语言中,您实际上可以使用 var 泛型定义字段/变量,例如:

    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。如果没有其他原因,最好显式键入您的变量,以提高可读性。查看变量的类型要容易得多,尤其是当您使用由多个开发人员维护的代码时。此外,显式键入变量可确保按预期使用,否则将引发可处理的异常,或者编译器将在构建解决方案时引发错误或警告。

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

    ...我怎么知道它是左手还是右手? 在我的例子中,手不是肩膀,而是手应该是 附在应该是因为肩膀使他们能够移动。 那么你将如何实现呢? – eersteam

    让我们设计一些接口和类来说明它们的用途,我将对您已经拥有的内容进行一些更改/添加所有这些都应该是相当简单的 - 看看Body 类上的AddLegs() 方法和看看LeftLeg 类。

    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();
            }
        }
    }
    

    【讨论】:

    • 我很抱歉,但让我们缓和一下,因为它仍然有点令人困惑。当我写 IShoulder rightHand 或 RightHand rightHand 时,我在创建对象之前定义了它的类型?另外我一直想问,如果我们使用 new 关键字,为什么我们需要定义一个类型,就像我们对任何其他对象(如集合)所做的那样? list mylist = new list 以及如何口头解决这个问题?像 RightHand 类的对象 rightHand 封装 RightHand/IShoulder?如果我错了,请纠正我
    • 我已经更新了我的答案,提供了有关键入字段/变量的更多信息。我会尝试给你一些解释,但是,这确实是一个非常广泛的主题,可能更适合你在有关一般 OOP 约定和实践的书籍或教程中阅读。
    • 我强烈反对“var”。在某些情况下它可以隐藏您的类型,但在大多数情况下,使用 var 在赋值语句的右侧声明类型会提高可读性和可维护性。
    • 如果你在那里声明new,当然可以,但是foreach(var item in myobject.GetCollection()) { /* .. */ } 没有给出任何线索。你可以不同意所有你想要的,但我只是对似乎是这个主题的初学者的人就可读性的一般意见发表声明。关于显式键入变量以确保正确使用和分配的观点也仍然非常有效。
    • 感谢 gimley 的深入回答,等待您实施示例还请提及当您定义和访问对象时谁在封装什么/谁在封装什么(这就是我想要的学习原创)
    【解决方案2】:

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

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

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

    // 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
        }
    }
    

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

    那么你什么时候会使用接口呢?举个例子,如果我的工作是遍历身体部位的集合并“戳”它们,我真的不在乎你给我什么特定类型的身体部位,我只需要戳它们。这是一个例子:

    // 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);
        }
    }
    

    【讨论】:

    • 谢谢,好吧,但我怎么知道它是左手还是右手?在我的例子中,手不是肩膀,但假设手应该连接到应该,因为肩膀使它们能够移动。那么你将如何实现呢?
    • 在这种情况下,您需要一个名为 Shoulder 的对象来实现 IShoulders,并且 IShoulders 接口应该添加一个名为 Arm 的属性,类型为 @987654327 的新接口@。我会用一个扩展的例子来更新我的答案。
    • @eersteam,如果你关心你是用左手还是右手工作,你就不会使用这个界面。我认为您的问题更多是因为对接口的用途缺乏了解,而不是关于封装。在我试图解决这个问题的地方查看我对我的答案的更新。我将如何实现这一点?我会将双手和肩膀与所有其他身体部位一起封装在身体类中。如果所需的手部运动有必要,身体(而不是肩部)将负责移动手部以及移动肩部。
    猜你喜欢
    • 1970-01-01
    • 2014-01-24
    • 1970-01-01
    • 2019-03-09
    • 1970-01-01
    • 1970-01-01
    • 2022-06-11
    • 2020-01-14
    • 1970-01-01
    相关资源
    最近更新 更多