Section 41.4: 인터페이스 구현 예제로써의 IComparable<T>

인터페이스에 대한 개념은 실제 어떻게 사용되는지를 직접 보기 이전에는 너무 추상적이어서 이해하기 어렵다고 느낄 수 있다. 그런 점에서, IComparableIComparable<T> 는 인터페이스가 실제로 어떻게 유용하게 사용될 수 있는지를 보여주는 좋은 예제가 될 수 있다.

온라인 판매점을 위한 프로그램을 작성한다는 가정을 해 보자. 판매하기 위한 다양한 물품 (item) 들이 있을 것이며, 각 물품들은 이름, ID 번호, 그리고 가격 정보를 가지게 될 것이다.

public class Item { public string name; // public 변수는 일반적으로 권장되지 않지만, public int idNumber; // 이 예제를 간단히 표현하기 위해 public decimal price; // 속성값 대신 public 변수를 사용한다. // 간략한 예제를 위해 본문 부분은 생략한다 }

이제 이 Item 들을 List<Item> 내에 저장을 해 둔 상태에서, 프로그램 어딘가에서 이 Item 들을 ID 번호 기준으로 오름차순 정렬을 수행하고자 하는 경우를 생각해 보자. 우리가 직접 정렬 알고리즘을 작성하는 대신, List<T> 에서 이미 제공하고 있는 Sort() 메소드를 이용할 수 있을 것이다. 그러나, 현재 구현된 Item 클래스만으로는 List<T> 로 하여금 어떤 순서로 정렬을 수행하여야 할지 알려줄 방법이 존재하지 않는다. 바로 이러한 경우에 IComparable 인터페이스를 사용할 수 있다.

CompareTo 메소드를 정확히 구현 (implement) 하려면, CompareTo 의 파라미터가 현재 값보다 작은 경우 양수를, 동일할 경우 0 을, 큰 경우 음수를 반환하도록 하여야 한다.

Item apple = new Item(); apple.idNumber = 15; Item banana = new Item(); banana.idNumber = 4; Item cow = new Item(); cow.idNumber = 15; Item diamond = new Item(); diamond.idNumber = 18; Console.WriteLine(apple.CompareTo(banana)); // 11 Console.WriteLine(apple.CompareTo(cow)); // 0 Console.WriteLine(apple.CompareTo(diamond)); // -3

해당 인터페이스를 구현한 Item 클래스 예제가 아래 소개되어 있다:

public class Item : IComparable<Item> { private string name; private int idNumber; private decimal price; public int CompareTo(Item otherItem) { return (this.idNumber - otherItem.idNumber); } // 간결한 예제를 위해 이외의 부분은 생략한다 }

겉으로 보기에, CompareTo 메소드가 하는 일은 두 항목의 ID 번호간의 차이를 반환하기만 할 뿐이다. 그러나 이 코드가 실제적으로 수행하는 역할은 무엇일까?

이제, List<Item> 객체에 대해 Sort() 메소드가 불리는 경우, 해당 List 는 정렬 순서대로 항목들을 배치하기 위해 자동적으로 ItemCompareTo 메소드를 호출하게 될 것이다. 더 나아가, 두개의 각기 다른 항목에 대한 상호 비교 기능을 이미 선언하였기에, 우리의 ItemList<T> 이외에도 두 객체간의 비교를 필요로하는 모든 객체들과 연동하여 작업을 수행할 수 있게 될 것이다.

본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 41.3: 인터페이스 관련 기본 사항들

인터페이스는 특정 기능의 "계약서 (contract)" 와도 같다고 알려져 있다. 이는 인터페이스가 메소드나 속성 등을 선언하고는 있지만 실제 구현을 하지는 않는다는 것을 의미한다.

따라서, 인터페이스는 클래스와 다르게:

  • 인스턴스화 (instantiate) 될 수 없다
  • 기능 자체를 제공하지는 않는다
  • 메소드만을 가질 수 있다 * (속성과 이벤트는 내부적으로 모두 메소드이다)
  • 인터페이스를 상속받는 작업은 "구현 (implement)" 한다고 한다
  • 클래스의 경우 하나의 클래스로부터만 상속을 받을 수 있지만, 인터페이스의 경우에는 복수개의 인터페이스를 "구현" 할 수 있다
public interface ICanDoThis{ void TheThingICanDo(); int SomeValueProperty { get; set; } }

몇가지 주목할 점:

  • "I" 접두어는 인터페이스에 대한 관용적 명명법 (naming convention) 이다.
  • 메소드의 본문 대신 세미콜론 ";" 만 기술한다.
  • 속성 (property) 도 내부적으로는 메소드이기에 함께 사용 가능하다.
public class MyClass: ICanDoThis { public void TheThingICanDo() { // 작업을 수행한다 } public int SomeValueProperty { get; set; } public int SomeValueNotImplentingAnything { get; set; } }
ICanDoThis obj = new MyClass(); // 문제 없음 obj.TheThingICanDo(); // 문제 없음 obj.SomeValueProperty = 5; // 에러, 인터페이스에는 정의되어 있지 않은 멤버이다 obj.SomeValueNotImplentingAnything = 5; // 클래스에만 존재하는 속성에 접근하고자 한다면 이를 "down cast" 하여야 한다 ((MyClass)obj).SomeValueNotImplemtingAnything = 5; // 문제 없음

이는 WinForms 나 WPF 등의 UI 프레임웍 기반의 코드를 작성할 때 매우 유용하다. 이러한 프레임웍들은 사용자 컨트롤들 (user control) 을 만들 때 특정 기반 클래스를 의무적으로 상속받게 되어 있어, 각기 다른 사용자 컨트롤들에 대한 추상화를 제공할 기회를 놓칠 수 있기 때문이다. 다음의 예제를 확인한다:

public class MyTextBlock: TextBlock { public void SetText(string str) { this.Text = str; } } public class MyButton: Button { public void SetText(string str) { this.Content = str; } }

문제가 되는 점은, 두 사용자 컨트롤들 모두 "텍스트" 관련 개념을 가지고는 있지만, 실제 속성 값 이름이 다르다는 점이다. 또한 이를 위해 추상 기반 클래스 (abstract base class) 를 만들 수도 없는데, 이는 각자가 의무적으로 두개의 다른 클래스를 이미 상속하고 있기 때문이다. 이런 문제를 인터페이스를 통해 해결할 수 있다.

public interface ITextControl { void SetText(string str); } public class MyTextBlock: TextBlock, ITextControl { public void SetText(string str) { this.Text = str; } } public class MyButton: Button, ITextControl { public void SetText(string str) { this.Content = str; } public int Clicks { get; set; } }

이제 MyButtonMyTextBlock 은 상호 교체가능 (interchangeable) 하다.

var controls = new List < ITextControls > { new MyTextBlock(), new MyButton() }; foreach(var ctrl in controls) { ctrl.SetText("This text will be applied to both controls despite them being different"); // 인터페이스에 해당 멤버가 존재하지 않으므로 아래 코드는 컴파일 에러를 발생시킨다. ctrl.Clicks = 0; // 첫번째 클래스가 실제 버튼 클래스가 아니므로 올바르지 않은 타입 변환으로 인해 런타임 에러를 발생시킨다. ((MyButton) ctrl).Clicks = 0; /* 해결책으로, 클래스의 타입을 먼저 확인해 볼 수 있다. 그러나 이러한 접근 방식은 잘못된 추상화의 조짐이므로 종종 옳지 않은 관행으로 여겨진다. */ var button = ctrl as MyButton; if (button != null) button.Clicks = 0; // 에러 발생하지 않음 }
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 41.2: 명시적인 인터페이스 구현 (implementation) 방법

공통의 메소드를 갖는 복수개의 인터페이스를 구현 (implement) 할 때, 해당 메소드를 호출하기 위해 어느 인터페이스가 사용되었는지에 따라 각기 다른 구현 내용이 제공되어야 한다면 명시적인 인터페이스 구현 방법을 사용하여야 한다. (만약 복수개의 인터페이스가 동일한 메소드를 갖지만 공통의 구현 내용을 제공하는 것이 가능하다면 굳이 명시적 인터페이스 구현 방법을 사용할 필요는 없다).

역주: 아래 예제에서 사용된 Chauffeur 라는 단어는 "운전기사" 를 지칭한다고 합니다.

interface IChauffeur { string Drive(); } interface IGolfPlayer { string Drive(); } class GolfingChauffeur: IChauffeur, IGolfPlayer { public string Drive() { return "Vroom!"; } string IGolfPlayer.Drive() { return "Took a swing..."; } } GolfingChauffeur obj = new GolfingChauffeur(); IChauffeur chauffeur = obj; IGolfPlayer golfer = obj; Console.WriteLine(obj.Drive()); // Vroom! Console.WriteLine(chauffeur.Drive()); // Vroom! Console.WriteLine(golfer.Drive()); // Took a swing...

이렇게 제공된 명시적 구현 내용은 해당 인터페이스를 통하지 않고는 호출이 불가능하다:

public class Golfer: IGolfPlayer { string IGolfPlayer.Drive() { return "Swinging hard..."; } public void Swing() { Drive(); // 컴파일 에러 : 존재하지 않는 메소드 } }

따라서, 명시적 인터페이스 구현 방법을 이용한 복잡한 코드는 별도의 private 메소드로 분리해 두는 것이 유리할 수 있다.

또한, 명시적 인터페이스 구현 방법은 당연하게도 해당 인터페이스에 실제로 존재하는 메소드에 대해서만 사용할 수 있다:

public class ProGolfer: IGolfPlayer { string IGolfPlayer.Swear() // 에러 { return "The ball is in the pit"; } }

유사하게, 클래스에 인터페이스를 선언하지 않은 채 해당 인터페이스에 대한 명시적 인터페이스 구현 방법을 사용하는 것 역시 에러를 발생시킬 것이다.

힌트:

명시적으로 인터페이스를 구현하는 방식은 더 이상 사용되지 않는 죽은 코드 (dead code) 방지를 위해서도 사용될 수 있다. 특정 메소드가 더 이상 필요하지 않아 인터페이스에서 제거될 경우, 컴파일러는 불필요하게 남아있는 구현부의 존재에 대해 알려줄 것이다.

유의사항:

프로그래머들은 인터페이스에 대한 계약 (contract) 이 타입에 대한 문맥에 관계 없이 동일하며 명시적 구현 내용이 호출시마다 다른 동작을 보여주지 않기를 기대할 것이다. 따라서 위 예제와는 다르게, IGolfPlayer.DriveDrive 가 동일한 동작을 수행할 수 있다면 그렇게 하는 것이 바람직하다.

본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 41.1: 인터페이스 구현 (implement) 하기

인터페이스란 해당 인터페이스를 '구현 (implement)' 하는 모든 클래스들에 명시된 메소드들이 존재함을 보장하기 위해서 사용된다. 인터페이스는 interface 키워드를 이용해 정의되며 각 클래스들은 자신의 클래스 이름 뒤에 : InterfaceName 구문을 추가함으로써 해당 인터페이스를 '구현' 할 수 있다. 또한 복수의 인터페이스들을 구현하기 위해서는 다음과 같이 쉼표를 이용해 각 인터페이스들을 구분해 기술해 줄 수 있다. : InterfaceName, ISecondInterface

public interface INoiseMaker { string MakeNoise(); } public class Cat: INoiseMaker { public string MakeNoise() { return "Nyan"; } } public class Dog: INoiseMaker { public string MakeNoise() { return "Woof"; } }

catdog 둘 모두 INoiseMaker 를 구현하도록 선언되었기에, 이들은 string MakeNoise() 메소드를 포함하고 있어야 하며 만약 포함하지 않을 경우 컴파일이 실패하게 된다.

본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 40.6: protected 액세스 한정자

protected 키워드는 중첩된 클래스, 속성, 메소드나 필드를 동일한 클래스 혹은 상속받은 클래스 내부에서만 사용 가능하도록 제한한다:

public class Foo() { protected void SomeFooMethod() { // 임의의 작업 수행 } protected class Thing { private string blah; public int N { get; set; } } } public class Bar(): Foo { private void someBarMethod() { SomeFooMethod(); // 상속된 클래스의 내부 var thing = new Thing(); // 중첩된 클래스 역시 사용 가능하다 } } public class Baz() { private void someBazMethod() { var foo = new Foo(); foo.SomeFooMethod(); // prptected 액세스 한정자로 인해 접근이 불가능하다 } }
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 40.5: internal 액세스 한정자

internal 키워드는 클래스 (중첩된 형식 포함), 속성, 메소드나 필드를 동일 어셈블리 내의 모든 사용자 (consumer) 들에게만 직접 접근 가능하도록 허용한다:

internal class Foo { internal string SomeProperty { get; set; } } internal class Bar { var myInstance = new Foo(); internal string SomeField = foo.SomeProperty; internal class Baz { private string blah; public int N { get; set; } } }

이는 테스트를 위한 어셈블리에서도 해당 코드에 접근 가능하도록 AssemblyInfo.cs 파일을 다음과 같이 수정함으로써 우회로를 제공할 수 있다:

using System.Runtime.CompilerServices; [assembly:InternalsVisibleTo("MyTests")]
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 40.4: protected internal 액세스 한정자

protected internal 키워드는 중첩된 클래스, 속성, 메소드나 필드를 동일 어셈블리 내에서 접근하거나 다른 어셈블리 내의 상속된 클래스 내에서 접근하는 경우에만 접근을 허용한다:

어셈블리 1

public class Foo { public string MyPublicProperty { get; set; } protected internal string MyProtectedInternalProperty { get; set; } protected internal class MyProtectedInternalNestedClass { private string blah; public int N { get; set; } } } public class Bar { void MyMethod1() { Foo foo = new Foo(); var myPublicProperty = foo.MyPublicProperty; var myProtectedInternalProperty = foo.MyProtectedInternalProperty; var myProtectedInternalNestedInstance = new Foo.MyProtectedInternalNestedClass(); } }

어셈블리 2

public class Baz: Foo { void MyMethod1() { var myPublicProperty = MyPublicProperty; var myProtectedInternalProperty = MyProtectedInternalProperty; var thing = new MyProtectedInternalNestedClass(); } void MyMethod2() { Foo foo = new Foo(); var myPublicProperty = foo.MyPublicProperty; // 컴파일 에러 var myProtectedInternalProperty = foo.MyProtectedInternalProperty; // 컴파일 에러 var myProtectedInternalNestedInstance = new Foo.MyProtectedInternalNestedClass(); } } public class Qux { void MyMethod1() { Baz baz = new Baz(); var myPublicProperty = baz.MyPublicProperty; // 컴파일 에러 var myProtectedInternalProperty = baz.MyProtectedInternalProperty; // 컴파일 에러 var myProtectedInternalNestedInstance = new Baz.MyProtectedInternalNestedClass(); } void MyMethod2() { Foo foo = new Foo(); var myPublicProperty = foo.MyPublicProperty; // 컴파일 에러 var myProtectedInternalProperty = foo.MyProtectedInternalProperty; // 컴파일 에러 var myProtectedInternalNestedInstance = new Foo.MyProtectedInternalNestedClass(); } }
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 40.3: private 액세스 한정자

private 키워드는 중첩된 클래스, 속성, 메소드나 필드를 클래스 내부에서만 사용 가능하도록 제한한다:

public class Foo() { private string someProperty { get; set; } private class Baz { public string Value { get; set; } } public void Do() { var baz = new Baz { Value = 42 }; } } public class Bar() { public Bar() { var myInstance = new Foo(); // 컴파일 에러 - private 액세스 한정자로 인해 접근 불가능하다 var someValue = foo.someProperty; // 컴파일 에러 - private 액세스 한정자로 인해 접근 불가능하다 var baz = new Foo.Baz(); } }
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

+ Recent posts