반사가 어떻게 코드 냄새로 이어지지 않습니까?
저는 저수준 언어에서 왔습니다 -C ++는 제가 프로그램하는 최고 수준입니다.
최근에 반사를 접하고 코드 냄새없이 사용할 수 있습니다.
제 생각에 실행하는 동안 클래스 / 메소드 / 함수를 검사하는 아이디어는 디자인의 결함을 가리습니다. 리플렉션 (시도하려는) 대부분의 문제는 다형성이나 상속의 사용과 함께 수 구축 생각합니다.
내가 잘못? 반사의 개념과 유용성을 오해하고 있습니까?
다른 솔루션이 실패하거나 구현하기 너무 번거 롭거나 사용하지 않을 때 Reflection을 사용할 때에 대한 좋은 설명을 찾고 있습니다.
이 낮은 수준의 윤활유를 깨달으십시오.
리플렉션은 정적 유형 시스템을 우회하는 데 가장 일반적으로 사용 몇 가지 흥미로운 사용 사례도 있습니다.
ORM을 작성합시다!
NHibernate 또는 대부분의 다른 ORM에 익숙하다면 다음과 같이 데이터베이스의 테이블에 매핑되는 클래스를 작성합니다.
// used to hook into the ORMs innards
public class ActiveRecordBase
{
public void Save();
}
public class User : ActiveRecordBase
{
public int ID { get; set; }
public string UserName { get; set; }
// ...
}
어떻게 작성하는 방법을 생각 하십니까? 음, 대부분의 ORM에서 메소드는 파생 클래스에있는 필드를 알지 못하지만 리플렉션을 사용하여 액세스 할 수 있습니다.Save()
그것의 완전한 수는 DataRow 개체에 필드를 복사하는 방법을 재정의하는 사용자를 요구하고, 형태 보증 된 방법으로 기능을 가지고 있고, 그 상용구 코드 및 확장을 많이 것입니다.
스텁!
Rhino Mocks 는 모의 프레임 워크입니다. 인터페이스 유형을 메소드에 전달하면 프레임 워크가 인터페이스를 구현하는 모의 객체를 동적으로 구성하고 인스턴스화합니다.
그녀의 프로그래머는 모의 객체에 대한 상용구 코드를 손에 들고 말할 수있는 프레임 워크가 원할까요?
메타 데이터!
다양한 용도로 사용할 수있는 속성 (메타 데이터)으로 메소드를 장식 할 수 있습니다.
[FilePermission(Context.AllAccess)] // writes things to a file
[Logging(LogMethod.None)] // logger doesn't log this method
[MethodAccessSecurity(Role="Admin")] // user must be in "Admin" group to invoke method
[Validation(ValidationType.NotNull, "reportName")] // throws exception if reportName is null
public void RunDailyReports(string reportName) { ... }
속성을 검사하는 방법을 검토해야합니다. 대부분의 .NET 용 AOP 프레임 워크는 정책 삽입에 속성을 사용합니다.
물론 동일한 종류의 코드를 인라인으로 말할 수 있습니다.
의존성 프레임 워크를 만들어 봅시다!
많은 IoC 컨테이너가 제대로 실행 되려면 어느 정도의 반사가 필요합니다. 예를 들면 :
public class FileValidator
{
public FileValidator(ILogger logger) { ... }
}
// client code
var validator = IoC.Resolve<FileValidator>();
IoC 컨테이너는 파일 유효성 검사기를 인스턴스화하고 ILogger 구현을 생성자에 전달합니다. 어떤 구현? 그것은 구현 방법에 달려 있습니다.
구성 파일에서 어셈블리 이름과 클래스를 지정 가정 해 보겠습니다. 언어는 클래스 이름을 복제로 사용하여 인스턴스화해야합니다.
타임에 구현을 알지 할 수있는 이름을 기반으로 클래스를 인스턴스화하는 형식 안전 방법이 없습니다.
늦은 바인딩 / 덕 타이핑
상호 작용에 대한 가능성이 있습니다. 가장 간단한 사용 사례로 로깅을 선택하겠습니다. 모든 것을 받아들이고 모든 속성을 파일로 작성는 로거를 작성 가정 해 보겠습니다.
public static void Log(string msg, object state) { ... }
당신은 할 수 있는 모든 정적 유형의 로그 메소드를 오버라이드 (재정의)하거나, 또는 당신은 대신 반사를 읽을 수 있습니다.
OCaml 및 Scala와 같은 일부 언어는 정적으로 확인 된 덕 타이핑 ( 구조적 타이핑 이라고 함 )을 지원하지만 인터페이스에 대한 타임 지식이 없습니다.
또는 Java 프로그래머가 알고대로 유형 시스템이 사용자의 방식대로 작동하여 모든 종류의 상용구 코드를 작성해야하는 경우가 있습니다. 동적 타이핑으로 얼마나 많은 디자인 패턴이 간단 하게 설명 할 수 있습니다.
유형 시스템을 우회하면 정적 유형에서 가능한 것보다 훨씬 더 코드를 약간 더 깔끔하게 처리 할 수 있습니다. 현대의 많은 정적 언어는 "심각한 경우 정적 입력, 필요한 경우 동적"이라는 황금률을 채택하여 정적 코드와 동적 코드 사이를 전환 할 수 있습니다.
Hibernate (O / R 매핑) 및 StructureMap (종속성 배치) 과 같은 프로젝트 는 Reflection 없이는 불가능합니다. 다형성 만으로이 문제를 어떻게 해결할 수 있습니까?
그 문제를 다른 방법으로 해결하기 클래스 가 너무 어렵게 만드는 것은 라이브러리가 계층 구조에 대해 직접적으로 알지 못하기 때문입니다. 그러나 예를 들어 필드 이름과 속성 이름 만 사용하여 데이터베이스의 임의의 데이터 행을 클래스의 속성에 매핑하려는 클래스의 구조를 알아야합니다.
반사는 매핑 문제에 특히 유용합니다 . 코드에 대한 필요 관습에 대한 아이디어 가 점점 더 대중화되고 수신이 수행되는 반사 필요 유형이 있습니다.
.NET 3.5 이상에서는 사용하는 대안이 있습니다. 이것들은 강력한 유형이며 Reflection을 사용하여 고전적으로 해결 된 많은 문제가 람다와 표현 트리를 다시 구현하여 다시 구현했습니다 ( Fluent NHibernate , Ninject 참조 ). 그러나 모든 언어가 다만 종류의 구조를 지원하는 것은 아닙니다. 당신은 기본적으로 사용할 수 없습니다.
어떤면에서 (그리고 이것으로 너무 많은 것을 쓰지 사용하지 않기 때문에), Reflection은 함수에서 무료로 제공되는 기능에 대한 지향 언어의 해결 방법 / 핵으로 자주 사용됩니다. 기능적 언어가 더 대중화되고 / 또는 더 많은 OO 언어가 더 많은 기능적 기능 (예 : C #)을 시작하면 리플렉션 구현이 점점 더 적게 사용되는 것을 의미합니다. 그러나 나는 플러그인과 같은 더 일반적인 응용 프로그램의 경우 (다른 응답자 중 한명이 유용하게 지적했듯이) 항상 주변에 있다고 생각합니다.
사실, 당신은 이미 매일 반사 시스템을 사용하고 있습니다 : 당신의 컴퓨터.
물론 클래스, 메서드 및 개체 대신 프로그램과 파일이 있습니다. 프로그램은 메서드가 개체를 만들고 수정하는 것처럼 파일을 만듭니다. 그러나 프로그램 자체 는 파일이고 일부 프로그램은 다른 프로그램을 검사하거나 생성 합니다!
Linux 설치가 반사적이어서 아무도 없다고 생각하지도 않고 OO 프로그램에 대해 무서운 것은 무엇입니까?
사용자 지정 속성으로 좋은 사용법을 보았습니다. 데이터베이스 프레임 워크와 같은.
[DatabaseColumn("UserID")]
[PrimaryKey]
public Int32 UserID { get; set; }
그런 다음 리플렉션을 사용하여 다음 필드에 대한 추가 정보를 얻을 수 있습니다. LINQ To SQL이 작업을 수행 할 확신합니다.
다른 예로는 테스트 프레임 워크가 있습니다.
[Test]
public void TestSomething()
{
Assert.AreEqual(5, 10);
}
반성없이 자주 반복해야합니다.
다음 시나리오를 고려하십시오.
- 테스트 케이스의 testXXX () 메소드와 같은 메소드 세트 실행
- GUI 빌더에서 속성 목록 생성
- 수업을 스크립팅 가능하게 만들기
- 내장 화 체계 구현
일반적으로 코드의 다른 곳에서 영향을받는 메서드 및 속성의 전체 목록을 반복하지 않고 C / C ++에서 작업을 수행 할 수 없습니다.
C / C ++ 프로그래머는 종종 인터페이스 설명 언어 를 사용하여 운영에 인터페이스를 노출합니다 (반영 형식 제공).
잘 정의 된 코딩 규칙과 결합 된 리플렉션 및 주석을 결합하게 사용하면 만연한 코드 반복을 방지하고 유지 관리 가능성을 사용할 수 있습니다.
반사는 강력하지만 쉽게 남용이 될 수있는 것 중 하나라고 생각합니다. 매우 목적을 위해 "파워 사용자"가 될 수있는 수있는 도구가 주어졌지만 완성 지향적 인 디자인을 대체하거나 (객체 지향이 모든 것을위한 솔루션이 아닌 것처럼) 사용되는 것이 아닙니다.
Java가 구조화되는 방식으로 인해 발생하는 실행시 메모리에서 클래스 계층 구조를 이미 지불하고 있습니다 (가상 메소드와 같은 것을 사용하지 않는 한 비용을 지불하지 않는 C ++와 비교). 따라서 완전히 차단되는 데 비용이 들지 않습니다.
리플렉션은 생성 화와 같은 작업에 유용합니다. Hibernate 또는 다이 제스터와 같은 것에서는 사용하여 자동으로 가장 잘 저장하는 방법을 사용할 수 있습니다. JavaBeans 모델은 메소드의 이름을 기반으로하지만 (의심스러운 결정, 인정합니다) 작성하는 것을 빌드하는 데 사용할 수있는 속성을 검사 할 수 있습니다. 최신 버전의 Java에서는 리플렉션이 주석을 유용하게 만듭니다. 소스 코드에 실행에 액세스 할 수있는 것을 사용하여 도구를 작성하고 메타 프로그래밍을 수행 할 수 있습니다.
Java 프로그래머로서 전체 경력을 쌓을 수 있으므로 처리하는 문제가 필요하지 않으므로 리플렉션을 사용할 수 없습니다. 반면에 특정 문제의 경우 매우 필요합니다.
대부분의 대부분의 개체를 처리해야하는 코드를 구현하는 데 사용됩니다. 예를 들어 ORM 매퍼는 사용자 정의 클래스에서 개체를 인스턴스화하고 데이터베이스 행의 값으로 채워야합니다. 이를 달성하는 가장 간단한 방법은 반성하는 것입니다.
사실, 당신은 부분적으로 옳고, 반사는 종종 코드 냄새입니다. 대부분의 경우 클래스와 함께 작업하고 반영 할 필요가 없습니다. 유형을 알고있는 경우, 성능, 가독성 및이 세상에서 좋은 모든 것을 의지 할 수 있습니다. 그러나 라이브러리, 프레임 워크 또는 일반 유틸리티를 작성하는 경우 리플렉션으로 가장 잘 처리되는 상황에 처하게됩니다.
이것은 내가 익숙한 Java로되어 있습니다. 다른 언어는 동일한 목표를 달성하는 데 사용할 수있는 기능을 제공하지만 Java에서는 리플렉션이 최상의 최상의 (때는로드되는 유일한) 솔루션 인 명확한 애플리케이션을 제공합니다.
NUnit과 같은 단위 테스트 소프트웨어 및 프레임 워크는 리플렉션을 사용하여 테스트 목록을 가져옵니다. 모듈 / 어셈블리 / 바이너리 (C #에서는 클래스로 표시됨)의 모든 테스트 스위트와 해당 스위트의 모든 테스트 (C #에서는 클래스의 메소드 임)를 찾습니다. NUnit을 사용하는 경우 예외 계약을 테스트하는 경우 예상되는 예외로 테스트를 표시 할 수 있습니다.
리플렉션없이 사용할 수있는 테스트 스위트와 각 스위트에서 사용할 수있는 테스트를 지정해야합니다. 또한 예외와 같은 것은 수동으로 테스트해야합니다. 내가 본 C ++ 단위 테스트 프레임 워크는이를 위해 매크로를 사용했지만 일부는 여전히 수동 이며이 디자인은 제한적입니다.
Paul Graham이 가장 잘 말할 수 있는 훌륭한 에세이 가 있습니다.
프로그램을 작성하는 프로그램? 언제 그렇게하고 싶습니까? Cobol에서 생각하면 자주는 아닙니다. Lisp에서 생각한다면 항상. 강력한 매크로의 예를 들어주고 거기에 말할 수있는 권한 여기에서 편리 할 것입니다! 어떻게에 대한? 하지만 내가 그렇게한다면 Lisp를 모르는 사람 횡설수설처럼 보일 것입니다. 그것이 의미하는 바를 이해하기 위해 필요한 모든 것을 설명 할 여지가 없습니다. Ansi Common Lisp에서 가능한 한 빨리 움직이려고 노력했지만 160 페이지까지 매크로를 사용하지 않습니다.
. . .
수년 동안 우리는 Viaweb에서 일하는 동안 많은 직업 설명을 읽었습니다. 매달 목공에서 새로운 경쟁자가 등장하는 것 같았습니다. 라이브 온라인 데모가 확인한 후 가장 먼저 할 일 목록을 것입니다. 몇 년 후에 나는 어떤 회사에 대해 걱정 해야할지 말하지 않을지 말할 수 있습니다. 직업 설명에 IT 풍미가 많을수록 회사는 덜 위험했습니다. 가장 안전한 것은 Oracle 경험을 원했던 것입니다. 당신은 걱정할 필요가 없습니다. 그들은 C ++ 또는 Java 개발자를 원합니다 말하면 안전했습니다. 그들은 Perl이나 Python 프로그래머를 원하고, 그것은 약간 무섭게 느껴질 것입니다. 그것이 바로 인 체계가 실제 해커에 의해 운영되는 회사처럼 들리기 시작했습니다. Lisp 해커를 찾는 채용 공고를 본 적이있는 사람
그것은 모두 빠른 개발에 관한 것입니다.
var myObject = // Something with quite a few properties.
var props = new Dictionary<string, object>();
foreach (var prop in myObject.GetType().GetProperties())
{
props.Add(prop.Name, prop.GetValue(myObject, null);
}
플러그인이 좋은 예입니다.
도구는 또 다른 예입니다-검사 도구, 빌드 도구 등.
제가 배우기 시작했을 때받은 ac # 솔루션의 예를 들어 보겠습니다.
여기에는 [Exercise] 속성으로 클래스가 포함되어 있고 각 클래스에는 구현되지 않은 메소드가 포함되어 있습니다 (NotImplementedException 발생). 솔루션에는 모두 실패한 단위 테스트도 있습니다.
목표는 모든 방법을 구현하고 모든 단위 테스트를 통과하는 것이 었습니다.
솔루션은 모든 클래스를 사용하여 인터페이스를 생성하는 인터페이스를 생성합니다.
우리는 나중에 우리 자신의 방법을 구현하고 요청하는 것으로, 나중에 우리가 구현 한 모든 새로운 방법을 포함하도록 사용자 인터페이스가 '마 법적으로'를 변경해야합니다.
매우 유용하지만 종종 잘 이해되지 않습니다.
이 아이디어는 GUI 개체 속성을 쿼리하여 사용자 지정 및 사전 구성을 위해 GUI에 제공하는 것입니다. 이제 사용이 확장되고 실행 가능했습니다.
편집 : 맞춤법
의존성에 매우 유용합니다. 지정된 특성을 사용하여 지정된 인터페이스를 구현하는로드 된 어셈블리 유형을 탐색 할 수 있습니다. 구성 구성 파일과 결합 된 클라이언트 코드를 수정하지 않고 새 상속 된 클래스를 추가하는 강력하고 강력한 방법을 추가합니다.
또한 기본 모델에 실제로 신경 쓰지 않고 직접 구조화되는 방식에 관심이있는 편집기를 수행하는 경우 ala System.Forms.PropertyGrid
)
작동 렉션이 작동하지 않습니다!
Python의 아주 간단한 예입니다. 세 가지 메소드가있는 클래스가 검증 가정합니다.
class SomeClass(object):
def methodA(self):
# some code
def methodB(self):
# some code
def methodC(self):
# some code
이제 다른 클래스에서 몇 가지 추가 동작으로 해당 메소드를 장식하려고합니다 (즉, 해당 클래스가 SomeClass를 모방하지만 추가 기능을 사용하기를 원합니다). 이것은 다음과 같이 간단합니다.
class SomeOtherClass(object):
def __getattr__(self, attr_name):
# do something nice and then call method that caller requested
getattr(self.someclass_instance, attr_name)()
카피 렉션을 사용하면 자주 언급 필요가없는 소량의 도메인 독립 코드를 사용할 수 있고 속성을 추가 / 제거 할 때와 같이 더 자주 변경해야하는 많은 도메인이 있습니다. 프로젝트에서 선언 된 규칙을 사용하면 특정 속성, 속성 등의 존재를 기반으로 공통 기능을 수행 할 수 있습니다. 서로 다른 도메인 객체 데이터 변환은 반사가 실제로 유용 한 가지 예입니다.
또는 규칙이 유지되는 한 (이 경우 속성 이름과 특정 속성이 일치하는 경우) 속성이 변경 될 때 변환 코드를 필요없이 데이터베이스에서 데이터 개체로 데이터를 변환하려는 도메인 내의 더 간단한 예 :
///--------------------------------------------------------------------------------
/// <summary>Transform data from the input data reader into the output object. Each
/// element to be transformed must have the DataElement attribute associated with
/// it.</summary>
///
/// <param name="inputReader">The database reader with the input data.</param>
/// <param name="outputObject">The output object to be populated with the input data.</param>
/// <param name="filterElements">Data elements to filter out of the transformation.</param>
///--------------------------------------------------------------------------------
public static void TransformDataFromDbReader(DbDataReader inputReader, IDataObject outputObject, NameObjectCollection filterElements)
{
try
{
// add all public properties with the DataElement attribute to the output object
foreach (PropertyInfo loopInfo in outputObject.GetType().GetProperties())
{
foreach (object loopAttribute in loopInfo.GetCustomAttributes(true))
{
if (loopAttribute is DataElementAttribute)
{
// get name of property to transform
string transformName = DataHelper.GetString(((DataElementAttribute)loopAttribute).ElementName).Trim().ToLower();
if (transformName == String.Empty)
{
transformName = loopInfo.Name.Trim().ToLower();
}
// do transform if not in filter field list
if (filterElements == null || DataHelper.GetString(filterElements[transformName]) == String.Empty)
{
for (int i = 0; i < inputReader.FieldCount; i++)
{
if (inputReader.GetName(i).Trim().ToLower() == transformName)
{
// set value, based on system type
loopInfo.SetValue(outputObject, DataHelper.GetValueFromSystemType(inputReader[i], loopInfo.PropertyType.UnderlyingSystemType.FullName, false), null);
}
}
}
}
}
}
// add all fields with the DataElement attribute to the output object
foreach (FieldInfo loopInfo in outputObject.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance))
{
foreach (object loopAttribute in loopInfo.GetCustomAttributes(true))
{
if (loopAttribute is DataElementAttribute)
{
// get name of field to transform
string transformName = DataHelper.GetString(((DataElementAttribute)loopAttribute).ElementName).Trim().ToLower();
if (transformName == String.Empty)
{
transformName = loopInfo.Name.Trim().ToLower();
}
// do transform if not in filter field list
if (filterElements == null || DataHelper.GetString(filterElements[transformName]) == String.Empty)
{
for (int i = 0; i < inputReader.FieldCount; i++)
{
if (inputReader.GetName(i).Trim().ToLower() == transformName)
{
// set value, based on system type
loopInfo.SetValue(outputObject, DataHelper.GetValueFromSystemType(inputReader[i], loopInfo.FieldType.UnderlyingSystemType.FullName, false));
}
}
}
}
}
}
}
catch (Exception ex)
{
bool reThrow = ExceptionHandler.HandleException(ex);
if (reThrow) throw;
}
}
아직 언급되지 않은 한 가지 사용법 : 리플렉션은 일반적으로 "느린"것으로 간주되지만 리플렉션을 사용하여 인터페이스 IEquatable<T>
가 존재할 때 와 같은 인터페이스를 사용하는 코드의 효율성을 향상시키고 그렇지 않은 경우 동등성을 확인하는 다른 수단을 사용하는 것이 가능합니다. 리플렉션이없는 경우 두 개체가 동일한 지 테스트하려는 코드 는 개체가 구현되었는지 여부 를 런타임에 사용 Object.Equals(Object)
하거나 확인 해야 하며, 그렇다면 개체를 해당 인터페이스로 캐스팅해야합니다. 두 경우 모두 비교되는 사물의 유형이 값 유형 인 경우 적어도 하나의 권투 작업이 필요합니다. Reflection을 사용하면 클래스가 특정 유형 에 대한 유형별 구현을 자동으로 생성 할 수 있습니다.IEquatable<T>
EqualityComparer<T>
IEqualityComparer<T>
T
, 해당 구현 IEquatable<T>
이 정의 된 Object.Equals(Object)
경우 사용하거나 그렇지 않은 경우 사용 합니다. EqualityComparer<T>.Default
특정 유형에 대해 처음 사용 하는 경우 T
시스템은 특정 유형이 구현하는지 여부를 한 번 테스트하는 데 필요한 것보다 더 많은 작업을 거쳐야합니다 IEquatable<T>
. 반면에 작업이 완료되면 시스템 EqualityComparer<T>
에서 해당 유형에 대한 사용자 정의 빌드 구현을 생성하므로 더 이상 런타임 유형 검사가 필요하지 않습니다 .
참고 URL : https://stackoverflow.com/questions/2195914/how-could-reflection-not-lead-to-code-smells
'ProgramingTip' 카테고리의 다른 글
C ++ STL 세트 차이 (0) | 2020.11.23 |
---|---|
왜 wait ()는 항상 루프 내에서 호출됩니까? (0) | 2020.11.23 |
Maven 프로젝트에 시스템 속성 지정 (0) | 2020.11.23 |
객체 지향 디자인과 관련된 구성이란 무엇입니까? (0) | 2020.11.23 |
테스트 및 개발에 사용할 수있는 SMTP 서버-실제로 메일을 배달하지 않습니다. (0) | 2020.11.23 |