객체지향 엘레베이터 만들기 winform

안녕하세요 윈도우폼을 처음 접한 완전초보입니다 제가 엘레베이터를 만들려는데 자꾸 절차지향적으로 주먹구구식으로 코딩을 한것같고 객체지향적으로 설계를 하려고 생각을해도 이해가 잘안됩니다 … 객체지향 엘레베이터를 만들려고하는데 큼직큼직하게 어떤식으로 구성하면 좋을지 알려주세요

조금 더 상세히 질문에 대한 답변을 달 수 있도록 설명을 바랄께요.

  • 엘레베이터의 정의
  • 엘레베이터를 만드는 목적 - 목적에 따라 모델링의 규모 및 방향성이 달라지므로
  • 본인이 생각하는 절차지향적 코드와 객체지향적 코드의 차이점
  • 이렇게 만들었다 코드 예시
  • 객체지향적으로 설계했을 때 어떻게 이해가 안되는지 좀 더 상세히 표현
    • 예를 들어 - 엘레베이터를 움직이는 주체는 누구인가?
  • 등등…
1 Like

최종 목적은 stocker 를 만드는게 목표인데 엘레베이터부터 만들어 보라고 해서 만들었는데 저는 elevator , elevatorManager , EventSystem , FloorUpDownButton 이렇게 객체별로 나눈게 아니라 한곳에 다모아서 만들었는 느낌이 듭니다 객체지향별로 설계를 하려면 port,vehicle,command,elevatorManager 등 하나하나 객체화 해서 관리하는방법을 생각하려고하는데 사실 어떤식으로해야할지 모르겠습니다 객체별로 열거형으로 상태변화에 따라 동작하게 해야하나요?

목적에 따라 달라집니다. 목적이

실제 엘레베이터(최종적으로 stocker)를 정확히 모델링 하는 것이 목적이라면 상태와 동작성을 그렇게 모델링 하는 것이 맞구요

객체지향 프로그래밍을 학습하기 위한 목적으로 엘레베이터를 모델링 하시는 것이라면 개체의 독립성, 개체와 개체의 연관성을 고려해서 모델링 하시는 것이 맞습니다.

어떤 것인지 모르겠네요.


객체지향적이냐? 로 보는 저의 기준은 개체로 모델링 되었느냐. 관련 상태와 행위가 그 개체에 부합하느냐 정도입니다. 나머지는 부수적인 것들이죠. 예를들어 일반화라는가 하는 것은 저는 부수적인 것이라 봅니다.
하지만 개체와 개체간 효율적인 인터페이싱을 하려면 일반화 작업이 이뤄져야 하기 때문에 중요하긴 합니다.

제가 아는 정보는 stocker 를 만드는데 SIM 라는 표현을 들었는데 제가 완전 초보라 어떤건지 몰라서 저는 elevator 이라고 하나로 만들었지만 엘레베이터라는게 문도있고 사람을 운반하는 vehicle도 있고 이걸 하나하나 객체화 한후 객체를 뭉쳐서 엘레베이터를 만들고 이걸 커맨드를 던져서 막 하라고 하는데 먼소린지 몰라서 질문을 못하겠내요 …

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Elevator.Device
{
internal class Port : BaseDevice
{
private PortState _portstate;

    public Port()
    {
        _portstate = PortState.None;
    }
}

}
using Elevator.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Elevator
{
internal class ElevatorManager
{
private BaseDevice basedevice = new BaseDevice();

    private CommandManager commandmanager = new CommandManager();

    internal BaseDevice Basedevice { get => basedevice; set => basedevice = value; }
    internal CommandManager Commandmanager { get => commandmanager; set => commandmanager = value; }

    public ElevatorManager()
    {

    }
}

}
using Elevator.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Elevator
{
internal class CommandManager
{

}

}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Elevator.Device
{
internal class Vehicle : BaseDevice
{
private VehicleState _state;
public Vehicle()
{
_state = VehicleState.None;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Elevator.Device
{
internal class BaseDevice
{
public int Floor;

    private Port port = new Port();
    private Vehicle vehicle = new Vehicle();

    internal Vehicle Vehicle { get => vehicle; set => vehicle = value; }
    internal Port Port { get => port; set => port = value; }
}

}
하는 방법을 보여주긴했는데 너무 빨라서 이것말고는 기억나는게 없어요 … 혹시 이거 보시면 먼가 말해주실게 있을가요 …?

객체지향을 연습하시려고 하는데 UI를 먼저 생각하시면 복잡해질겁니다. 콘솔앱 + 라이브러리 프로젝트로 시작해보세요.

완성하고나서 UI 프로젝트로 옮겼을때 출력 및 상호작용을 거의 이어주기만 하면 끝나는 수준이어야 잘 된 설계라고 볼 수 있습니다.

2 Likes

감이 안 잡힐 땐 인터페이스와 테스트코드를 먼저 작성해보세요.

스토커… 혹시 신성이신가요?

소프트웨어를 설계할 때는, 해결하고자 하는 문제의 영역(도메인) 의 최상단 객체부터 하단 방향으로 내려 가는 것이 좋습니다.

예를 들어 스토커가 최상단 객체이면, 스토커부터 정의하는 것입니다.

간편하게는 아래와 같이 만들 수도 있습니다.

class Stocker
{
   public void Elevate() { // ... }
}

위 코드에서는 승강의 책임(Elevate)을 스토커가 지고 있습니다.
그러나, 이 책임을 다른 객체에 위임할 수도 있는데, 이때, 새로운 객체, Elevator가 자연스럽게 도입됩니다.

class Stocker
{
   private Elevator _elevator;

   public void Elevate() =>  _elevator.StartMoveUp();

   public async Task<ElevationResult> ElevateAsync(ElevationRequest request)
   {
      var destination = request.ToPositionVector();
      var after = await _elevator.MoveToAsync(destination);
      return after == destination ? EvevationResult.Succed()
            : ElevationResult.Fail(_elevator.Logs.ToErrorMessage());
   }
}

새로운 코드에서는 승강의 책임이 스토커에서 엘리베이터로 위임되었고, Elevate 메서드는 ElevationRequest 를 PositionVector로 변환하는 책임만 수행합니다.

이렇게 책임을 분리하는 과정을 "리디자인"이라고 하는데, 그 목적은 "단일 책임 원칙"에 보다 충실한 코드를 만들기 위해서입니다.

그러나, 이 또한 복잡도라 해당 도메인에서 그렇게 요구하지 않으면, 굳이 복잡하게 구현할 필요는 없이 첫 번째 방식으로 구현해도 됩니다.

책임의 위임은 객체 사이의 상호 작용을 위한 관계 설정을로 귀결됩니다.
객체는 다른 객체와 직접적으로 상호 작용할 수도 있지만,

→ : 의존 방향.

Stocker → Elevator

class Stocker
{
   private Elevator _elevator;

중간 객체를 통해 간접적으로 상호작용할 수도 있습니다.
그런데, 중간 객체는 관계 설정에 다양한 방법이 있을 수 있습니다.

Stocker → ElevationController ← Elevator

class ElevationController { // ... }

class Stocker
{
   private ElevationController _controller; 
// ...

class Elevator
{
   private ElevationController _controller;

또는

ElevationController → Stocker
ElevationController → Elevator

class ElevationController 
{
   private Stocker _stocker;
   private Elevator _elevator;
   // ... 

또는

Stocker → ElevationController → Elevator

class Stocker
{
   private ElevationController _controller;
   // ...

class ElevationController
{
   private Elevator _elevator;
   // ...

위의 의존 구조들 중 어떤 걸 선택해도 정답이나 오답은 아닙니다.
도메인의 특성, 개발자의 선호 등 여러 가지 측면이 영향을 미칩니다.

그래서, 코딩하기 전에는 도메인 지식이 명확한 사람에게 소프트웨어가 무엇을 해야하는 지를 명확히 전달 받아야 합니다.

어떤 도메인에서는 개발자가 도메인 전문가 일 수도 있지만, 일반적으로는 개발 의뢰자가 전문가입니다. 그래서, 보통 개발을 시작할 때는, 개발 의뢰자에게 소프트웨어 요구사항(SR)을 받게 됩니다.
받지 못한 경우, 인터뷰를 통해 개발하는 측에서 작성해야 합니다.

요구 사항은 개발 중에 수시로 변경될 수 있습니다.
이러한 요구 사항 변경에 대응할 수 있도록 설계하자는 주장이 애자일 운동이고, 그것을 위한 대표적 방법론이 SOLID 원칙입니다.

시스템을 구성하는 객체들을 하나씩, 추가해다 보면 완성 단계에 이르게 되는 것입니다.

완성된 코드가 나오기까지 리펙토링 또는 리디자인을 통해 완성된 코드의 몇 배의 코드가 사라지는데, 이것은 지극히 정상입니다.
객체 지향 언어의 가장 큰 장점은 이러한 과격한 코드 변경을 안전하게 수행할 수 있게 해준다는 점입니다.

반대로, 리팩토링/리디자인을 게을리하면, 코드는 금방 성장 한계에 부딪히게 되어, 전체를 새롭게 써야 할 수도 있습니다.

@yjs 님은 현재 리펙토링/리디자인으로 해결할 수 있는 단계가 아니라, 새롭게 설계해야 할 시점 같아 보입니다.

무엇이 최상단 객체인지 확실하지 않은 상태로 보아, 도메인 지식이 불완전하고, 도메인 요구 사항도 받지 못 했거나, 작성하지 않은 듯 보이기 때문입니다.

도메인 모델 설계가 익숙하지 않다면, UI 프레임워크(윈폼)로 개발을 시작하는 것은 추천하지 않습니다. 왜냐하면, 도메인 모델이 져야 할 책임이 코드 비하인드에 들어가기 쉬워지기 때문입니다. (현재 직면한 상태)

윈폼과 같은 UI 도구는 착장에 불과합니다.
도메인 모델이 잘 설계되어 있으면, 여기에 윈폼 옷을 입히든, WPF 옷을 입히던, 심지어 Console 옷을 입히든, 아무런 문제가 되지 않습니다.

7 Likes

객체지향에 대해 조금이라도 아시면, 이 글 읽지 마시기 바랍니다.

2024년 8월에 C# 파일 하나가 97000 줄인 것을 보았습니다.

제가 생각하는 객체지향 구현의 제 1 원칙은
코드를 클래스 단위로 쪼개야 하고,
한 클래스의 줄 수가 작아야 한다는 것입니다.

자신이 작성한 코드 전체의 줄 수와 클래스 수를 비교하면,
그 코드가 객체지향 쪽으로 향하는지 일단 알 수 있다고 생각합니다.

저는 개인적으로 C# 파일 하나의 줄 수가 200~500 줄 정도를 넘으면,
갱신에 부담을 느끼지 않을까 생각합니다.

(이 글에서 클래스 하나가 파일 하나라고 생각합니다.)

1 Like

System.Collections.Generic.List는 IList, IList, IReadOnlyList 의 다형성을 충족하게 구현되어 있습니다.
매우 자주 사용하는 Collection 중의 하나 인데 각 인터페이스에는 무엇이 정의 되어 있는지 최 상위 까지 살펴보시면 좋을 것 같네요.

사족으로 자바에서 public 클래스와 파일 이름이 일치하는 것은 필수적인 규칙입니다. 이를 준수하지 않으면 컴파일 오류가 발생합니다. 또한 하나의 public 클래스만 존재할 수 있습니다.