مفهوم و کاربرد منطق XOR در اعتبارسنجی داده‌ ها

The concept and application of XOR logic in data validation

مقدمه

منطق XOR یا «یا این یا آن» (Exclusive OR) یکی از مفاهیم بنیادی و اساسی در منطق ریاضی، جبر بولی و طراحی مدار های دیجیتال است که کاربرد بسیار گسترده‌ ای در علوم کامپیوتر، به‌ویژه در اعتبارسنجی داده‌ها (Data Validation) و طراحی ساختارهای داده‌ای دارد.

این عملگر منطقی، که اغلب با نماد XOR  نمایش داده می‌شود، برخلاف عملگر OR معمولی (شامل یا فراگیر)، زمانی خروجی درست (true یا ۱) برمی‌گرداند که فقط یکی از دو گزاره یا ورودی درست باشد. در صورتی که هر دو ورودی همزمان درست باشند (مانند OR)، یا هر دو نادرست باشند، خروجی آن غلط (false یا ۰) خواهد بود.

در حوزه برنامه‌نویسی و طراحی فرم‌های ورودی (Input Forms) یا مدل‌های داده‌ای (Data Transfer Objects - DTOs)، منطق XOR نقش حیاتی در اعمال محدودیت‌های انحصاری ایفا می‌کند. کاربرد اصلی آن در جایی است که انتخاب همزمان دو یا چند گزینه به‌طور منطقی و تجاری مجاز نیست. به عنوان مثال، در یک سیستم ثبت سفارش، کاربر ممکن است مجبور باشد فقط یکی از «روش تحویل فوری» یا «تحویل عادی» را انتخاب کند، اما هرگز نمی‌تواند هر دو را به‌طور همزمان انتخاب نماید.

استفاده صحیح از منطق XOR باعث می‌شود که سازگاری و صحت داده‌ها از همان ابتدا در سطح بالاترین لایه اعتبارسنجی (Validation Layer) تضمین شود و از ارسال داده‌های متناقض به لایه‌های پایین‌تر (مانند سرویس یا دیتابیس) جلوگیری شود.

تعریف ریاضی و جدول درستی XOR

عملگر XOR، که مخفف Exclusive OR است، در منطق بولی بر روی دو متغیر بولی $A$ و $B$ تعریف می‌شود.

 

جدول درستی (Truth Table)

جدول درستی XOR به‌طور کامل نشان می‌دهد که چگونه ورودی‌ها به خروجی تبدیل می‌شوند:

ABA XOR Bتوضیح000هر دو نادرست هستند (نتیجه نادرست است).011فقط B درست است (نتیجه درست است).101فقط A درست است (نتیجه درست است).110هر دو درست هستند (نتیجه نادرست است).

فرمول‌های جبری

در جبر بولی، می‌توان عبارت منطقی $A \oplus B$ را با استفاده از عملگرهای پایه AND ($\land$ یا $&&$)، OR ($\lor$ یا $||$) و NOT ($\neg$ یا $!$) تعریف کرد.

فرمول استاندارد برای تعریف XOR به شرح زیر است:

[A \oplus B = (A \land \neg B) \lor (\neg A \land B) ]

این فرمول دقیقاً بیان می‌کند: «A درست باشد و B نادرست باشد، یا A نادرست باشد و B درست باشد.»

تعریف جایگزین (با استفاده از OR فراگیر)

همچنین می‌توان XOR را با استفاده از عملگر OR (فراگیر) و NAND تعریف کرد:

[A \oplus B = (A \lor B) \land \neg (A \land B) ]

این فرمول می‌گوید: «حداقل یکی از A یا B درست باشد و این درست نباشد که هر دوی A و B درست باشند.» این تعریف ساختار انتخابی بودن را به وضوح نشان می‌دهد.

 

تفاوت کلیدی XOR با سایر عملگرهای بولی

درک تفاوت XOR با OR و AND برای کاربردهای اعتبارسنجی حیاتی است:

  1. AND ($\land$):
    • شرط: هر دو باید درست باشند.
    • مثال کاربردی: برای ارسال سفارش، هم «آدرس» و هم «شماره تماس» باید پر شده باشند. (نیاز به هر دو)
  2. OR ($\lor$):
    • شرط: حداقل یکی باید درست باشد.
    • مثال کاربردی: کاربر باید یا شماره موبایل را وارد کند یا شماره تلفن ثابت. (نیاز به یکی یا هر دو)
  3. XOR ($\oplus$):
    • شرط: دقیقاً یکی باید درست باشد.
    • مثال کاربردی: کاربر باید یا کد تخفیف مخصوص مشتریان جدید را وارد کند یا کد تخفیف مخصوص خرید عمده، اما نمی‌تواند هر دو را همزمان اعمال کند. (نیاز به انحصاری بودن)

منطق XOR حالت «انحصاری» را مدل‌سازی می‌کند که در بسیاری از قوانین کسب‌وکار (Business Rules) ضروری است.

 

کاربرد گسترده XOR در اعتبارسنجی داده‌ها (Data Validation)

در محیط‌های نرم‌افزاری مدرن، اعتبارسنجی داده‌ها در لایه‌های مختلفی انجام می‌شود: فرانت‌اند، بک‌اند (مانند Middleware یا Decoratorها)، و دیتابیس. منطق XOR به‌طور خاص برای سناریوهایی که در آن‌ها "اگر فیلد X پر باشد، فیلد Y باید خالی باشد" کاربرد دارد.

سناریوهای رایج:

  1. تعیین مکان (Location): در یک فرم ثبت نام، کاربر باید آدرس فیزیکی (House Address) یا مختصات GPS (Lat/Lng) را وارد کند، اما نه هر دو.
  2. نوع پرداخت: پرداخت باید یا از طریق کارت اعتباری (Credit Card Details) انجام شود یا از طریق درگاه بانکی شخص ثالث (Third-Party Gateway Token)، اما نه هر دو در یک تراکنش.
  3. ارسال جایگزین: در سیستم‌های اطلاع‌رسانی، پیام باید یا از طریق ایمیل ارسال شود یا از طریق پیامک (SMS)، اما نباید هر دو کانال به‌طور همزمان استفاده شوند (مگر در حالت جایگزینی شکست‌ خورده).

پیاده‌سازی عملی در TypeScript / NestJS

فرض کنید در حال توسعه یک API با NestJS هستیم و یک DTO برای ثبت‌نام جلسات آموزشی داریم که در آن باید مشخص شود جلسه حضوری است یا آنلاین، اما نمی‌تواند هر دو باشد.

مدل داده (DTO) فرضی:

// src/dto/create-session.dto.ts

export class CreateSessionRequestDto {
  title: string;

  // حالت حضوری: نیاز به شناسه مکان فیزیکی دارد
  locationId?: number; 

  // حالت آنلاین: نیاز به لینک جلسه آنلاین دارد
  onlineMeetingUrl?: string; 

  // این دو فیلد باید XOR باشند
}

برای اعمال این قانون، می‌توان از منطق XOR مستقیم در سرویس استفاده کرد، هرچند روش استاندارد استفاده از Decorator های سفارشی است.

پیاده‌سازی اعتبارسنجی مستقیم (ساده‌ترین حالت):

import { BadRequestException } from '@nestjs/common';

// در لایه سرویس یا کنترلر پس از دریافت DTO
function validateSessionData(dto: CreateSessionRequestDto) {
  const hasLocation = !!dto.locationId;
  const hasUrl = !!dto.onlineMeetingUrl;

  // پیاده‌سازی XOR: (A && !B) || (!A && B)
  const isXorValid = (hasLocation && !hasUrl) || (!hasLocation && hasUrl);

  if (!isXorValid) {
    throw new BadRequestException(
      'جلسه باید یا به صورت حضوری (با locationId) یا به صورت آنلاین (با onlineMeetingUrl) تعریف شود، اما نه هر دو.'
    );
  }
  // ادامه منطق کسب‌وکار
}

 

طراحی Validator سفارشی در NestJS با استفاده از

برای داشتن کدی تمیزتر و قابلیت استفاده مجدد (Reusable)، استاندارد این است که یک Decorator اعتبارسنجی سفارشی ایجاد کنیم. این کار با استفاده از تابع registerDecorator از کتابخانه class-validator انجام می‌شود.

تعریف Decorator

این Decorator دو نام فیلد را به عنوان ورودی می‌گیرد و منطق XOR را بر اساس مقادیر آن فیلدها در شیء ورودی بررسی می‌کند.

// src/common/decorators/is-xor-fields.decorator.ts

import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';

/**
 * Decorator سفارشی برای اعمال منطق XOR بین دو فیلد مشخص شده.
 * تضمین می‌کند که دقیقاً یکی از field1 یا field2 مقدار داشته باشد.
 */
export function IsXorFields(field1: string, field2: string, validationOptions?: ValidationOptions) {
  return function (object: Record<string, any>, propertyName: string) {
    registerDecorator({
      name: 'isXorFields',
      target: object.constructor,
      options: validationOptions,
      propertyName, // این همان فیلدی است که این Decorator روی آن اعمال می‌شود، گرچه منطق روی فیلدهای دیگر است.
      validator: {
        validate(_: any, args: ValidationArguments) {
          // args.object حاوی کل شیء (DTO) است
          const obj = args.object as any;
          
          // مقادیر فیلدهای مورد نظر را استخراج می‌کنیم
          const a = obj[field1];
          const b = obj[field2];

          // تعریف XOR: (A && !B) || (!A && B)
          // در جاوااسکریپت، 'truthy' بودن به معنی "پر بودن" در نظر گرفته می‌شود.
          return (a && !b) || (!a && b);
        },
        // پیام پیش‌فرض در صورت خطا
        defaultMessage: () => `Validation failed for XOR condition: باید فقط یکی از ${field1} یا ${field2} مقدار داشته باشد.`,
      },
    });
  };
}

استفاده از Decorator در DTO

اکنون می‌توانیم Decorator را مستقیماً روی کلاسی که اعتبارسنجی انحصاری را نیاز دارد، اعمال کنیم. توجه داشته باشید که این Decorator را معمولاً روی یک فیلد فرضی یا اختیاری در DTO اعمال می‌کنیم، زیرا اعتبارسنجی سطح کلاس را انجام می‌دهد.

// src/dto/create-partner.dto.ts

import { IsOptional } from 'class-validator';
import { IsXorFields } from '../common/decorators/is-xor-fields.decorator';

// ما این Decorator را روی یک فیلد قرار می‌دهیم، اما در واقع کل کلاس را اعتبارسنجی می‌کند.
// اگرچه در NestJS می‌توانید از @Validate (در سطح کلاس) نیز استفاده کنید، 
// استفاده از این روش (اعمال روی یکی از فیلدها) برای خوانایی بیشتر در پروژه‌های بزرگ مرسوم است، 
// هرچند ممکن است از نظر فنی سطح اعمال، سطح فیلد باشد.

@IsXorFields('gymId', 'personalTrainingPlaceId', { 
    message: 'برای تعیین مکان، باید دقیقاً یکی از gymId یا personalTrainingPlaceId مقدار داشته باشد.',
})
export class CreatePartnerRequestDto {
  
  @IsOptional()
  gymId?: number;

  @IsOptional()
  personalTrainingPlaceId?: number;
}

در این حالت، اگر هر دو پر باشند یا هر دو خالی باشند، NestJS با استفاده از class-validator خطای Validation Error را برمی‌گرداند و پیام سفارشی ما نمایش داده می‌شود.

 

تست‌های واحد (Unit Testing) برای اطمینان از صحت عملکرد

صحت اعمال منطق XOR نیازمند پوشش کامل چهار حالت ممکن در جدول درستی است. 
 اگر از NestJS و supertest استفاده شود، تست‌ها به این شکل خواهند بود:

// نمونه تست (فرض شده)
import { Test, TestingModule } from '@nestjs/testing';
import { ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { CreatePartnerRequestDto } from '../dto/create-partner.dto';

describe('CreatePartnerController (XOR Validation)', () => {
  let app;

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      // ... تعریف Controller و Providerها ...
    })
    .overridePipe(new ValidationPipe({ 
        transform: true, 
        whitelist: true, 
        forbidNonWhitelisted: true,
        // اطمینان از استفاده از خطاهای سفارشی
    }))
    .compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  // تست 1: فقط gymId پر است (موفق)
  it('should allow creation when only gymId is provided', async () => {
    await request(app.getHttpServer())
      .post('/partners')
      .send({ gymId: 101 })
      .expect(201); // یا هر کد موفقیت دیگر
  });

  // تست 3: هر دو پر هستند (شکست - خروجی 0 XOR)
  it('should reject creation when both gymId and personalTrainingPlaceId are provided', async () => {
    await request(app.getHttpServer())
      .post('/partners')
      .send({ gymId: 101, personalTrainingPlaceId: 50 })
      .expect(400) // Bad Request
      .then(response => {
        expect(response.body.message).toContain('دقیقاً یکی از gymId یا personalTrainingPlaceId');
      });
  });

  // تست 4: هیچکدام پر نیستند (شکست - خروجی 0 XOR)
  it('should reject creation when neither field is provided', async () => {
    await request(app.getHttpServer())
      .post('/partners')
      .send({})
      .expect(400) 
      .then(response => {
        expect(response.body.message).toContain('دقیقاً یکی از gymId یا personalTrainingPlaceId');
      });
  });
});

 

تضمین سازگاری در سطح دیتابیس

اعتبارسنجی در لایه بک‌اند بسیار مهم است، اما برای تضمین نهایی یکپارچگی داده‌ها، باید محدودیت‌های معادل آن در سطح پایگاه داده نیز اعمال شود. در صورتی که داده‌ای از طریق یک مسیر غیرمستقیم (مثلاً یک اسکریپت مهاجرت یا اتصال مستقیم به دیتابیس) وارد شود، دیتابیس باید از پذیرش آن جلوگیری کند.

برای پایگاه‌های داده رابطه‌ای مانند PostgreSQL، می‌توان از قیدهای CHECK برای اعمال منطق XOR استفاده کرد.

مثال قید CHECK در PostgreSQL

با فرض ساختار جدولی که از gym_id و training_id استفاده می‌کند، قید XOR به شکل زیر نوشته می‌شود:

CREATE TABLE partners (
    id SERIAL PRIMARY KEY,
    gym_id INTEGER,
    training_id INTEGER,
    -- سایر فیلدها...
    
    -- اعمال منطق XOR: (A AND NOT B) OR (NOT A AND B)
    CONSTRAINT chk_exclusive_location
    CHECK (
        (gym_id IS NOT NULL AND training_id IS NULL)
        OR 
        (gym_id IS NULL AND training_id IS NOT NULL)
    )
);

این قید تضمین می‌کند که رکورد تنها در صورتی ذخیره شود که دقیقاً یکی از ستون‌ها دارای مقدار (Not Null) باشد. تلاش برای درج یا به‌روزرسانی رکوردی که این شرط را نقض کند، منجر به خطای سیستمی (Database Constraint Violation Error) خواهد شد.

 

جمع‌بندی و نتیجه‌گیری

منطق XOR یک ابزار قدرتمند و ریاضی‌ محور برای اعمال محدودیت‌های انحصاری دوگانه بر داده‌ها است. این منطق به مهندسان نرم‌افزار این امکان را می‌دهد که قوانین کسب‌وکار پیچیده‌ای را که نیازمند انتخاب دقیقاً یکی از گزینه‌های موجود است، به‌صورت شفاف و قابل تست پیاده‌سازی کنند.

مزایای کلیدی استفاده از XOR در اعتبارسنجی:

  1. وضوح منطقی: کد را خواناتر می‌کند و هدف از اعتبارسنجی (انتخاب انحصاری) را مستقیماً منعکس می‌نماید.
  2. پوشش کامل حالات: به‌طور سیستمی چهار حالت ممکن را پوشش می‌دهد و از ورود داده‌های مبهم (همزمان یا هیچ‌کدام) جلوگیری می‌کند.
  3. قابلیت تست بالا: اجرای تست‌های واحد برای بررسی چهار حالت ممکن، بسیار ساده و قابل اعتماد است.
  4. اعتبار چند لایه‌ای: قابلیت پیاده‌سازی در لایه مدل داده (DTO)، لایه سرویس و لایه دیتابیس، امنیت داده‌ها را در تمام مراحل تضمین می‌کند.

به‌کارگیری این منطق در معماری‌های مدرن، اعتبار و سازگاری داده‌ها را به‌شدت ارتقا داده و بار اعتبارسنجی تکراری در لایه‌های پایین‌تر را کاهش می‌دهد.