부모클래스 관련 질문입니다.

안녕하세요. 최대한 개발패턴을 지켜보려고 계속 분리를 하고 있는데… 갑자기 궁금한 점이 생겼습니다.
하나의 유저컨트롤을 여러 화면에서 가져다 쓸 경우 호출하는 부모의 클래스로 형변환 후 클래스내 함수를 호출이 필요할 경우 어떻게 처리를 해야하나요??

형 비교문을 넣어서 강제 변환을 하는 방법밖에 없는건가요??
이러면 뭔가 개발패턴이 아닌거 같기도 하는데…

부모 A , B
부모클래스 호출 함수 a ( a 함수에 한해서만 기능 동일)
자식 C

//형 비교문 없이 A , B로 변환을 하고 싶은데 가능할까요??
(A or B) C.parent().a()

2개의 좋아요

부모 폼에 인터페이스를 구현하시면 캐스팅이 필요 없어집니다.

1. 부모 클래스에 적용할 인터페이스 정의

public interface IParentInterface
{
    // 공통 함수 선언
    void CommonFunction();
}

2. 호출하는 Window에 인터페이스 구현

public partial class MainWindow : Window, IParentInterface
{
    public MainWindow()
    {
        InitializeComponent();

        childControl.MyParent = this;
    }

    // IParentInterface의 메서드 실제 구현
    public void CommonFunction()
    {
        MessageBox.Show("CommonFunction이 호출되었습니다");
    }
}

3. 자식 컨트롤 상세 구현

public partial class MyChildControl : UserControl
{
    // 부모 인터페이스를 참조할 속성
    public IParentInterface MyParent { get; set; }

    public MyChildControl()
    {
        InitializeComponent();
    }

    // 버튼 클릭 시 부모의 함수를 호출
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // 부모 메서드 호출
        MyParent?.CommonFunction();
    }
}

이렇게 되면 구체적으로 부모 Window가 어떤 Window냐를 따질 필요없이 IParentInterface를 구현하는 부모 Window는 무조건 자식 컨트롤에서 CommonFunction를 호출할 수 있습니다.

2개의 좋아요

와… 이런 방법이 있었네요~!! 감사합니다. 또 하나 배워가네요!!

2개의 좋아요

나이가 들어서 그런가 이게 무슨 의미인지 몇 번 봐도 감을 잡을 수가 없네요.

3개의 좋아요

흠… 어떤 부분이 이해가 안되시는지 모르겠지만 부가설명을 위해 코드를 짧게나마 작성해뒀는데… 여튼 자식클래스에서 부모클래스로 접근해서 부모클래스 내부 함수를 이용하려면 부모클래스에 접근해서 캐스팅을 해줘야 하잖아요?? 유저컨트롤(자식클래스)을 여러 화면에 붙였을 경우 부모컨트롤(부모클래스)을 캐스팅을 해줘야 하는데 부모컨트롤(부모클래스)이 전부다 다릅니다. 이럴 경우 전부다 IF문으로 비교를 해서 캐스팅을 해줄텐데 이걸 안하고 부모클래스의 타입을 불러와 캐스팅을 시켜줄 수 있는지가 궁금했던 겁니다. 윗분은 다른 대안을 알려주신거고요~

1개의 좋아요

"부모/자식"이라는 표현은 Is 관계에도, Has 관계에도 적용이 되기 때문에

// Is 관계
class Parent { }
class Child : Parent { }
// Has 관계
class ParentControl 
{
   ChildControl _child; 
}
class ChildControl { }

구체적 코드 없이 혹은, 잘못 설계된 코드를 바탕으로 그 실질을 파악하기가 힘들었습니다.

만약 질문이 아래와 같았다면, 질문의 요지를 파악하는 게 더 용이했을 것 같습니다.

자식 콘트롤에서 부모 콘트롤의 메서드를 어떻게 호출하나요?

Has 관계인 경우, 부모가 자식에 대한 참조를 보유하기 때문에, 의존 관계는

Parent → Child

입니다. 이 관계를 훼손 시키지 않으면서, 다시 말하면 순환 참조 없이, 사용할 수 있는 가장 간편한 통신 수단은 이벤트입니다.

// Has 관계
class Parent 
{
   // Parent.g.cs
   // Child _child; 
   public Parent() 
   {
      InitializeComponent(); // creates Parent.g.cs
      _child.SomethingHappened += OnSomethingHappened;
   }
   void OnSomeThingHappened(object sender?, string message)
   {      
      DoSomething(message);
   }
   void DoSomething(string action) { }
}

class Child
{
   public event EventHandler<string> SomethingHappened; 

   void OnStartButtonClicked(object? sender, EventArgs e)
   {
      SomethingHappened?.Invoke("Start");
   } 

   void OnStopButtonClicked(object? sender, EventArgs e)
   {
      SomethingHappened?.Invoke("Stop");
   } 
}

형식 안정성을 가미한다면,

interface IHaveEvent
{
   event EventHandler<string> SomethingHappened { get; }
}

class Child : IHaveEvent
{
   public event EventHandler<string> SomethingHappened {get;} = delegate { };
   // ...
}
// Has 관계
class Parent 
{
   // ...
   public Parent() 
   {
      // ... 
      if( _child is IHaveEvent publisher)
      {
         publisher.SomethingHappened += OnSomethingHappened;
      }
      else
      {
         throw new InvalidOperationException("_child shoule be IHaveEvent");
      }
   }
   // ...
}

UI 프레임워크가 UI 요소 간 통신을 위한 편리한 수단을 제공하지 않는다면, 구조적으로는,

  • 통신 서비스 객체
  • global 정적 저장소(Dictionary 보다는 Stack 이나 Queue)
  • 파일 시스템

언어적으로는,

  • event 시스템

을 이용할 수 있습니다.

4개의 좋아요

has-a / is-a 관계를 말씀 하셔서 한마디 보태면
저도 가끔 질문글들 볼때 부모 자식 관계라 하는데
이게 has 인지 is 인지 헤깔릴 때가 종종 있더라고요.

그래서 개인적 생각으로 이걸 통일 시키면 어떨까 싶은데

is-a 관계일 때는 .net에서는 base 키워드가 있죠.
(java는 super) 그래서 is-a 일땐 base child라고 하고

has-a의 경우 VisualTreeHelper에서 있듯
GetChild, GetParent 가 있듯 부모 자식 혹은 parent child 라고 부르는게 어떨까 싶네요.

2개의 좋아요

아래 주신 답으로도 충분하지만
부모 클래스에 인터페이스 달고
VisualTreeHelper로도 찾을수 있습니다.

폰이라 소스는 생략할게요…

인터페이스는 처음 질문해주신 케이스에도 자주 쓰지만 반대의 경우도 많이 씁니다.
하나의 부모 클래스 + 다양한 자식 클래스인 경우입니다.

예컨대, List와 같이 Generic List에 인터페이스를 담을 경우,
특정한 타입에 구애받지 않고 자식 요소들을 활용할 수 있습니다.

인터페이스 정의

public interface IHasId
{
    int Id { get; set; }
    void SendPacket();
}

서로 다른 class 정의

// 클래스1. ucDisplay
public class ucDisplay : IHasId
{
    public int Id { get; set; }
    public ucDisplay(int id)
    {
        Id = id;
    }

    public void SendPacket()
    {
        // ...
    }
}

// 클래스2. ucTag
public class ucTag : IHasId
{
    public int Id { get; set; }
    public ucTag(int id)
    {
        Id = id;
    }

    public void SendPacket()
    {
        // ...
    }
}

:star: 부모 객체에서의 활용

public partial class Form1 : Form
{
    // IHasId 인터페이스를 구현한 객체들을 담는 리스트 (형식 상관X)
    private List<IHasId> objList = new List<IHasId>();

    private void Form1_Load(object sender, EventArgs e)
    {
        // ucDisplay와 ucTag 객체를 생성하여 objList 리스트에 추가
        // 형식이 달라도 상관없다👍
        objList.Add(new ucDisplay(101));
        objList.Add(new ucTag(202));

        // 리스트의 각 요소 Id를 콘솔에 출력, SendPacket 메서드를 호출
        foreach (var item in objList)
        {
            Console.WriteLine("Id: " + item.Id);
            item.SendPacket();
        }
    }
}

제가 생각하는 인터페이스 장점은 이겁니다.

“형식의 구분없이 접근 가능하다”

이를테면 “var” 키워드가 가진 역할이랄까요.
아무튼 도움되셨길 바랍니다.:slightly_smiling_face:

1개의 좋아요

아하… 컨트롤에 다른 컨트롤 넣은 상황에서 .parent 키워드로 부모컨트롤에 접근을 하니 부모 , 자식이라 표현을 했는데 상속관계에서도 부모 , 자식이란 표현을 쓰는군요;; 몰랐습니다. 상속관계는 기본 , 파생이라 표현하는지 알고;; 이해가 안되실만 하셨네요~! 이벤트를 통한 접근 샘플도 감사합니다!

1개의 좋아요

부모컨트롤에 자식컨트롤을 넣은 상황에서 자식컨트롤에서 .parent 키워드로 부모에 접근을 하길래 부모 , 자식이라 표현했는데 공통된 용어로 쓰는지 몰랐습니다 ㅎㅎ;;

UI 프레임워크는 보통, 참조 방향을 거스르는 통신을 ( 부모 → 자식 참조관계에서, 자식 → 부모 방향으로의 통신) 위해 이벤트를 제공하는 경우가 많습니다.

  • WPF 에서는 RoutedEvent 가 버블업되고,
  • 블레이저에서는 EventCallBack 을 제공합니다.
  • 데이터 바인딩도 PropertyChanged 이벤트에 기반합니다.
  • 심지어, Html 에서도 DOM 이벤트가 root 요소 방향으로 연쇄 전달됩니다.

따라서 이벤트는 매우 보편적 통신 수단이라 할 수 있습니다.
보편적 방식은 예측성을 높이는 중요한 요소입니다.

프레임워크 사용자에게는,

역방향 통신을 위한 이벤트가 있을 거야!

코드 가독성 측면에서도,

당연히 이벤트로 처리했겠지!

따라서, 이벤트를 제 1 수단으로 사용하는 것이 코드의 보편성을 높이는 전략이라고 할 수 있습니다.

개인적인 생각인데요. wpf event는 그냥 사용하지 말고 behavior로 바꿔서 view model에서 다뤄야

2개의 좋아요

공감 한표 ㅎ