Revit 애드인을 개발하던 중, 생성한 PathOfTravel 라인 중에 내 애드인에서 만든 라인만 제거하는 기능을 추가하게 되었다. 여러 방식으로 구현할 수 있겠지만, 싱글톤 패턴을 이용해서 내가 만든 라인들의 리스트를 관리하는 방식을 선택하게 되었다. 일종의 애드인 내 전역변수로의 활용인 셈이다. 이 과정에서 개념적으로만 알고 있던 싱글톤 패턴을 직접 구현하는 과정을 되짚어본다.
싱글톤 패턴이란?
싱글톤 패턴(Singleton Pattern)은 클래스의 인스턴스를 하나만 생성하도록 보장하는 디자인 패턴이다. 즉, 프로그램이 실행되는 동안 해당 클래스의 객체는 단 하나만 존재하며, 이 인스턴스는 어디서든지 동일하게 접근할 수 있다. 그럼 싱글톤 패턴은 언제 사용할까? 주로 전역적으로 접근 가능한 객체가 필요할 때(이번에 내가 사용한 경우이다.), 여러 객체가 공통의 상태를 공유해야할 때 등이 있겠다. 그럼 바로 구현 방법을 살펴보자.
싱글톤 패턴의 구현 (C# 예제)
기본 싱글톤 패턴 구현
public class Singleton
{
// 정적 변수로 클래스의 유일한 인스턴스를 저장합니다.
private static Singleton _instance;
// 외부에서 인스턴스를 생성하지 못하도록 생성자를 private으로 설정합니다.
private Singleton()
{
// 생성자 내부에는 초기화 작업이 들어갈 수 있습니다.
}
// Singleton 인스턴스에 접근할 수 있는 정적 메서드를 제공합니다.
public static Singleton Instance
{
get
{
// 인스턴스가 없으면 생성하고, 있으면 기존 인스턴스를 반환합니다.
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
// 예제 메서드: 싱글톤 객체가 하는 작업
public void DoSomething()
{
Console.WriteLine("싱글톤 인스턴스에서 메서드 실행");
}
}
사용 방법
class Program
{
static void Main(string[] args)
{
// 싱글톤 인스턴스를 얻고 메서드 호출
Singleton singleton1 = Singleton.Instance;
singleton1.DoSomething();
// 다른 곳에서 다시 싱글톤 인스턴스를 얻어도 같은 인스턴스가 반환됩니다.
Singleton singleton2 = Singleton.Instance;
// 두 인스턴스가 동일한지 확인
Console.WriteLine(singleton1 == singleton2); // True 출력 (같은 인스턴스)
}
}
Eager Initialization vs Lazy Initialization
위에서 구현한 기본 예제는 멀티스레딩 환경에서 안전하지 않다. 여러 스레드가 동시에 싱글톤 인스턴스를 요청할 때, 두 개의 인스턴스가 생성될 위험이 있다. 이를 예방하기 위해 초기화 시기를 조절할 수 있는데, 초기화 시기에 따라 Eager Initialization과 Lazy Initialization으로 나눌 수 있다. 직역하면 빠른 초기화와 느린 초기화 정도가 되겠다. 멀티스레드 환경에서 안전한 싱글톤을 구현하기 위해서는 Eager Initialization 방식을 채택하여 클래스가 로드될 때 미리 인스턴스를 생성하여 멀티스레드 환경에서 안전하도록 한다. Lazy Initialization 방식은 메모리 자원 낭비를 막기 위해 필요할 때까지 생성을 최대한 늦추는 초기화 방법이다. 아래는 Eager Initialization 방식의 예시이다.
public class Singleton
{
// 정적 변수에 미리 인스턴스를 생성해 둡니다.
private static readonly Singleton _instance = new Singleton();
// 생성자를 private으로 설정하여 외부에서 인스턴스 생성을 막습니다.
private Singleton()
{
}
// 미리 생성된 인스턴스를 반환하는 정적 프로퍼티
public static Singleton Instance
{
get
{
return _instance;
}
}
public void DoSomething()
{
Console.WriteLine("스레드 안전한 싱글톤에서 작업 수행");
}
}
애드인에서 실전 사용 예시
내가 제작한 애드인에서 사용된 예시를 살펴보면, DrawCircle() 메서드로 특정 원을 그리고, 해당 원의 ElementId를 리턴으로 받아온다. 그렇게 생성한 원을 나중에 제거 기능을 위해 AddinStorage라고 하는 싱글톤 인스턴스에 리스트로 저장해 둔다.
원을 그리고 싱글톤 인스턴스에 저장하는 부분
ElementId circleId = DrawCircle(doorPoint); // 문 위치에 원을 그려 강조 표시하고 ElementId 저장
if (circleId != null)
{
AddinStorage.Instance.AddCircleId(circleId); // 그린 원의 ElementId를 AddinStorage 싱글톤 패턴에 저장
}
private ElementId DrawCircle(XYZ center)
{
double radius = 2.5; // 반지름 설정
Plane plane = Plane.CreateByNormalAndOrigin(XYZ.BasisZ, center);
Arc circle = Arc.Create(plane, radius, 0, 2 * Math.PI);
SketchPlane sketchPlane = SketchPlane.Create(_doc, plane);
ModelCurve modelCurve = _doc.Create.NewModelCurve(circle, sketchPlane); // 원을 ModelCurve로 그리기
// **가시성 및 순서 조정** - OverrideGraphicSettings 적용
OverrideGraphicSettings ogs = new OverrideGraphicSettings();
Color red = new Color(255, 0, 0);
ogs.SetProjectionLineColor(red); // 색상 설정
ogs.SetProjectionLineWeight(9); // 선 두께 설정
_doc.ActiveView.SetElementOverrides(modelCurve.Id, ogs); // 활성 뷰에서 모델 라인의 그래픽 설정을 덮어쓰기
return modelCurve?.Id; // 그린 원의 ElementId 반환
}
싱글톤 패턴 클래스 AddinStorage.cs
public class AddinStorage
{
private static AddinStorage _instance;
private List<ElementId> _createdCircleIds;
private AddinStorage()
{
_createdCircleIds = new List<ElementId>();
}
public static AddinStorage Instance
{
get
{
if (_instance == null)
{
_instance = new AddinStorage();
}
return _instance;
}
}
public List<ElementId> CreatedCircleIds
{
get { return _createdCircleIds; }
}
public void AddCircleId(ElementId id)
{
_createdCircleIds.Add(id);
}
public void ClearCircleIds()
{
_createdCircleIds.Clear();
}
}
내 애드인에서 제작된 circle만 제거하는 remove 기능 부분
// 생성한 원 삭제
List<ElementId> circleIds = AddinStorage.Instance.CreatedCircleIds;
foreach (ElementId circleId in circleIds)
{
doc.Delete(circleId);
}
AddinStorage.Instance.ClearCircleIds();
발견된 추가적인 문제점
이렇게 remove 기능으로 제거한 원을 Undo(Ctrl+Z) 기능으로 Revit에서 살려내면 제거된 원이 다시 살아난다. 그런데 문제점은 되살아난 해당 Element가 싱글톤 인스턴스의 리스트에 다시 복원되지 않아서 remove 기능을 다시 사용하고자 할 때 제거되지 않는 문제가 존재한다. 결국 AddinStorage 클래스의 ClearCircleIds() 메서드를 없애고, remove 부분에서도 AddinStorage.Instance.ClearCircleIds()를 실행하지 않는 것으로 해결하였다. 즉, 생성된 원들의 ElementId List는 삭제 뒤에도 계속 싱글톤 인스턴스에 남아있게 되는 것이다. 그렇다면 어디에선가는 Clear를 해 주어야 하는데, Clear는 새로운 원들을 그릴 때 Clear하는 것으로 수정하였다.
'🔨 개발 > 🏢 C# + Revit API' 카테고리의 다른 글
Windows 운영체제 환경에서의 소프트웨어 배포: .exe와 .msi 파일의 차이 (0) | 2024.09.24 |
---|---|
WiX Toolset을 이용한 .msi 설치파일 생성 및 revit add-in 배포 (3) | 2024.09.23 |